diff --git a/.gitignore b/.gitignore index b272176..6015372 100644 --- a/.gitignore +++ b/.gitignore @@ -40,5 +40,5 @@ build/ .DS_Store ### data ### -*.db +givehope.db /data/ \ No newline at end of file diff --git a/pom.xml b/pom.xml index 85afbce..0f3156b 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ javafx-maven-plugin 0.0.8 - ui.GiveHopeGIU + app.Main diff --git a/src/main/java/app/GiveHopeApp.java b/src/main/java/app/GiveHopeApp.java deleted file mode 100644 index 9738139..0000000 --- a/src/main/java/app/GiveHopeApp.java +++ /dev/null @@ -1,4 +0,0 @@ -package app; - -public class GiveHopeApp { -} diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java index 7cc167d..a1c0d3e 100644 --- a/src/main/java/app/Main.java +++ b/src/main/java/app/Main.java @@ -1,63 +1,83 @@ package app; +import domain.organization.Organization; +import integration.InnsamlingskontrollenClient; import application.security.PasswordHasher; import application.user.UserRegister; import domain.donation.Donation; -import domain.organization.Organization; import domain.user.User; -import integration.InnsamlingskontrollenClient; import integration.security.Sha256PasswordHasher; import java.math.BigDecimal; +import java.sql.SQLException; import java.util.List; import persistence.DonationDao; import persistence.OrganizationDao; import persistence.UserDao; import persistence.UserRepository; import persistence.db.Database; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; -public class Main { +public class Main extends Application{ public static void main(String[] args) { - try { - Database.getConnection(); - System.out.println("Database connected"); - - UserDao userDao = new UserDao(); - OrganizationDao orgDao = new OrganizationDao(); - DonationDao donationDao = new DonationDao(); - - PasswordHasher hasher = new Sha256PasswordHasher(); - UserRepository userRepo = new UserDao(); - UserRegister userReg = new UserRegister(hasher, userRepo); - - // Test brukerregistrering - userReg.execute("testuser", "12345678", "passord123", "test@example.com"); - System.out.println("Inserted user!"); - - User user = userDao.findByEmail("test@example.com").orElseThrow(); - System.out.println("Hentet bruker: " + user.getUsername() + ", id=" + user.getID()); - - // Hent og lagre organisasjoner fra Innsamlingskontrollen - System.out.println("Henter organisasjoner fra Innsamlingskontrollen..."); - Organization[] fetchedOrgs = InnsamlingskontrollenClient.fetchOrganizations(); - for (Organization org : fetchedOrgs) { - orgDao.insert(org); + launch(args); + } + + @Override + public void start(Stage stage) throws Exception { + Database.initializeDatabase(); + + OrganizationDao orgDao = new OrganizationDao(); + Organization[] orgs = InnsamlingskontrollenClient.fetchOrganizations(); + for (Organization org : orgs) { + orgDao.insert(org); } - System.out.println("Lastet inn " + fetchedOrgs.length + " organisasjoner"); - List orgs = orgDao.getAll(); - System.out.println("Organisasjoner i DB: " + orgs.size()); - System.out.println("Første org: " + orgs.get(0).getName() + ", org_number=" + orgs.get(0).getOrgNumber()); + addTestdata(); - // Test donasjon med første org fra DB - Donation donation = new Donation(new BigDecimal("100.00"), user, orgs.get(0)); - donationDao.insert(donation); - System.out.println("Inserted donation, id=" + donation.getId()); + Parent root = FXMLLoader.load(getClass().getResource("/view/MainView.fxml")); + Scene scene = new Scene(root, 1275, 725); + + stage.setTitle("GiveHope"); + stage.setScene(scene); + stage.show(); + } - List donations = donationDao.findByUser(user.getID()); - System.out.println("Antall donasjoner for bruker: " + donations.size()); + private void addTestdata() throws SQLException { + UserDao userDao = new UserDao(); + DonationDao donationDao = new DonationDao(); + OrganizationDao orgDao = new OrganizationDao(); - } catch (Exception e) { - e.printStackTrace(); + if (userDao.existsByEmail("test@test.no")) return; + + PasswordHasher hasher = new Sha256PasswordHasher(); + String hashedPassword = hasher.hash("test"); + User user = new User("test", "12345678", hashedPassword, "test@test.no"); + userDao.insert(user); + + Organization org = orgDao.getAll().get(0); + + donationDao.insert(new Donation(new BigDecimal(100), user, org)); + donationDao.insert(new Donation(new BigDecimal(250), user, org)); + donationDao.insert(new Donation(new BigDecimal(500), user, org)); + donationDao.insert(new Donation(new BigDecimal(300), user, org)); + donationDao.insert(new Donation(new BigDecimal(1000), user, org)); + donationDao.insert(new Donation(new BigDecimal(700), user, org)); + donationDao.insert(new Donation(new BigDecimal(250), user, org)); + + if (!userDao.existsByEmail("test2@test.no")) { + String hashedPassword2 = hasher.hash("test2"); + User user2 = new User("test2", "87654321", hashedPassword2, "test2@test.no"); + userDao.insert(user2); + + Organization org2 = orgDao.getAll().get(1); + + donationDao.insert(new Donation(new BigDecimal(150), user2, org2)); + donationDao.insert(new Donation(new BigDecimal(400), user2, org2)); + donationDao.insert(new Donation(new BigDecimal(600), user2, org2)); + } } - } } diff --git a/src/main/java/application/security/PasswordHasher.java b/src/main/java/application/security/PasswordHasher.java index d708691..03e2564 100644 --- a/src/main/java/application/security/PasswordHasher.java +++ b/src/main/java/application/security/PasswordHasher.java @@ -1,5 +1,9 @@ package application.security; +/** + * Interface that creates a "contract" for which methods the class that implements it, needs. + * Interface is created for SHA256PasswordHasher. + */ public interface PasswordHasher { String hash(String password); diff --git a/src/main/java/application/user/UserLogin.java b/src/main/java/application/user/UserLogin.java deleted file mode 100644 index e116498..0000000 --- a/src/main/java/application/user/UserLogin.java +++ /dev/null @@ -1,36 +0,0 @@ -package application.user; - -import application.security.PasswordHasher; -import domain.user.User; -import persistence.UserRepository; - -public class UserLogin { - - private final PasswordHasher hasher; - private final UserRepository repo; - - public UserLogin(PasswordHasher hasher, UserRepository repo) { - this.hasher = hasher; - this.repo = repo; - } - - public User execute(String login, String password) { - // normalize (trim etc.) - login.trim().toLowerCase(); - - User user = repo.findByLogin(login) - .orElseThrow(() -> new IllegalArgumentException("Invalid credentials")); - - // hash password (optional) - String hashedInput = hasher.hash(password); - - // compare hashed input with stored hash - if (!hashedInput.equals(user.getPassword())) { - throw new IllegalArgumentException("Invalid password"); - } - - System.out.println("Loggin in"); - // return user (if match) - return user; - } -} diff --git a/src/main/java/application/user/UserRegister.java b/src/main/java/application/user/UserRegister.java index 337b378..adc2107 100644 --- a/src/main/java/application/user/UserRegister.java +++ b/src/main/java/application/user/UserRegister.java @@ -4,16 +4,40 @@ import domain.user.User; import persistence.UserRepository; +/** + * The UserRegister class is responsible for handling the user registration process. + * It ensures that the provided user details meet the necessary criteria and that + * the username and email are not already in use. It also hashes the user's password + * before saving the user information into the repository. + */ public class UserRegister { private final PasswordHasher hasher; private final UserRepository repo; - public UserRegister(PasswordHasher hasher, UserRepository repo) { + /** + * Constructs a {@code UserRegister} instance responsible for handling the user registration process. + * It ensures user details meet validation criteria, such as uniqueness of username and email, + * and hashes passwords before saving user information into the repository. + * + * @param hasher a {@code PasswordHasher} implementation used to hash user passwords. + * @param repo a {@code UserRepository} implementation responsible for storing user data. + */ + public UserRegister(PasswordHasher hasher, UserRepository repo) { this.hasher = hasher; this.repo = repo; } + /** + * Executes a user registration. + * Validation checks, checks if username or e-mail already exists. + * Hashes inputted password using PasswordHasher, that uses SHA256PasswordHasher. + * Creates user object, and inserts it into UserRepository using insert method. + * @param username + * @param phoneNumber + * @param password + * @param email + */ public void execute(String username, String phoneNumber, String password, String email) { username = username.trim(); email = email.trim().toLowerCase(); diff --git a/src/main/java/application/user/UserSignIn.java b/src/main/java/application/user/UserSignIn.java new file mode 100644 index 0000000..be6b159 --- /dev/null +++ b/src/main/java/application/user/UserSignIn.java @@ -0,0 +1,57 @@ +package application.user; + +import application.security.PasswordHasher; +import domain.user.User; +import persistence.UserRepository; + +/** + * The UserSignIn class handles the process of user authentication. + * It verifies the input credentials (login and password) against the stored user data. + * The password is validated through hashing and comparison with the stored hash. + */ +public class UserSignIn { + + private final PasswordHasher hasher; + private final UserRepository repo; + + /** + * Constructs a {@code UserSignIn} instance responsible for user authentication. + * It handles the process of verifying user credentials by comparing the provided + * input with stored data in the repository and validating the password through hashing. + * + * @param hasher a {@code PasswordHasher} implementation used to hash and validate user passwords. + * @param repo a {@code UserRepository} implementation that stores user authentication data. + */ + public UserSignIn(PasswordHasher hasher, UserRepository repo) { + this.hasher = hasher; + this.repo = repo; + } + + /** + * Execute methods, that takes in inputted login and password. + * Checks via UserRepository if login exists in database. + * Compares hashed input password with hashed password in database. + * @param login inputted login by user + * @param password inputted password by user + * @return user object, if it exists. + */ + public User execute(String login, String password) { + // normalize (trim etc.) + login.trim().toLowerCase(); + + User user = repo.findByLogin(login) + .orElseThrow(() -> new IllegalArgumentException("Invalid credentials")); + + // hash password (optional) + String hashedInput = hasher.hash(password); + + // compare hashed input with stored hash + if (!hashedInput.equals(user.getPassword())) { + throw new IllegalArgumentException("Invalid password"); + } + + System.out.println("Loggin in"); + // return user (if match) + return user; + } +} diff --git a/src/main/java/application/user/UserStatistics.java b/src/main/java/application/user/UserStatistics.java index 5fe67d4..f284efc 100644 --- a/src/main/java/application/user/UserStatistics.java +++ b/src/main/java/application/user/UserStatistics.java @@ -8,9 +8,9 @@ /** * Provides statistical information about a user's donation activity. * Acts as a service layer between the application and {@link DonationDao}, + * showing relevant userstatistics to the user. * exposing user-friendly methods for retrieving donation history, - * total amounts, and favorite organizations. - */ + */ total amounts, and favorite organizations. public class UserStatistics { private DonationDao donationDao; @@ -23,22 +23,20 @@ public UserStatistics() { /** * Returns the top three organizations that the user has donated to most frequently. - * - * @param user the user whose favorite organizations are to be retrieved; must not be null + * @param user the user whose favorite organizations are going to be returned. * @return a list of up to three formatted strings, each representing an organization - * and its donation count; empty list if the user has no donations + * and its donation count. Returns empty list if the user has no donations. */ - public List userFavoriteOrganizations(User user) { - List favoriteOrganizations = donationDao.getFavoriteOrganizations(user.getID()); - return favoriteOrganizations; + public List userFavoriteOrganization(User user) { + List favoriteOrganization = donationDao.getFavoriteOrganization(user.getID()); + return favoriteOrganization; } /** * Returns a formatted list of all donations made by the user. * Each entry contains the donation amount, date, and organization. - * - * @param user the user whose donation history is to be retrieved; must not be null - * @return a list of formatted donation strings; empty list if the user has no donations + * @param user the user whose donation history is returned. + * @return a list of formatted donation strings. Returns empty list if the user has no donations */ public List userDonations(User user) { List userDonationList = donationDao.getUserDonations(user.getID()); @@ -47,9 +45,8 @@ public List userDonations(User user) { /** * Returns the total amount donated by the user across all donations. - * - * @param user the user whose total donation amount is to be calculated; must not be null - * @return a string representation of the total donated amount; {@code "0"} if no donations exist + * @param user the user whose total donation amount is returned. + * @return a string representation of the total donated amount. Returns "0" if no donations exist */ public String userTotalDonationAmount(User user) { String totalDonations = donationDao.getTotalDonationAmount(user.getID()); @@ -58,9 +55,8 @@ public String userTotalDonationAmount(User user) { /** * Returns the total number of donations made by the user. - * - * @param user the user whose donation count is to be retrieved; must not be null - * @return a string representation of the total number of donations; {@code "0"} if none exist + * @param user the user whose donation count is returned. + * @return a string representation of the total number of donations. Returns "0" if none exist. */ public String getTotalDonationsMade(User user) { String totalDonationsMade = donationDao.getTotalDonationsMade(user.getID()); diff --git a/src/main/java/domain/donation/Cause.java b/src/main/java/domain/donation/Cause.java index ae9311d..a69854e 100644 --- a/src/main/java/domain/donation/Cause.java +++ b/src/main/java/domain/donation/Cause.java @@ -1,3 +1,6 @@ package domain.donation; +/** + * Enum class that contains 5 different cause types + */ public enum Cause { HEALTH, EMERGENCY_RELIEF, CHILDREN, ENVIRONMENT, CONFLICTS; } diff --git a/src/main/java/domain/donation/Donation.java b/src/main/java/domain/donation/Donation.java index ff49864..9835c00 100644 --- a/src/main/java/domain/donation/Donation.java +++ b/src/main/java/domain/donation/Donation.java @@ -7,6 +7,10 @@ import java.time.LocalDateTime; import java.util.Objects; +/** + * Donation object that defines what a Donation is. + * Provides set and get methods. + */ public class Donation { private Long id; //get set to null by default, // Gets set to a value by database when registered? @@ -15,6 +19,13 @@ public class Donation { private final User user; private final Organization organization; + /** + * Donation takes in parameters that is needed to define what a donation is. + * Requires both user and organization to be not null. + * @param amount + * @param user + * @param organization + */ public Donation(BigDecimal amount, User user, Organization organization) { Objects.requireNonNull(amount, "Amount cannot be null"); if (amount.compareTo(BigDecimal.ZERO) <= 0) { @@ -27,6 +38,15 @@ public Donation(BigDecimal amount, User user, Organization organization) { this.organization = Objects.requireNonNull(organization, "Organization cannot be null"); } + + /** + * Sets the database-generated ID for this donation. + * Can only be set once — throws if the ID is already assigned. + * + * @param id the ID to assign; must be a positive non-null value + * @throws IllegalStateException if the ID has already been set + * @throws IllegalArgumentException if {@code id} is null or not positive + */ public void setId(Long id) { if (this.id != null) { throw new IllegalStateException("ID is already set"); @@ -37,22 +57,47 @@ public void setId(Long id) { this.id = id; } + /** + * Returns the unique database ID of this donation. + * + * @return the donation ID + */ public long getId() { return id; } + /** + * Returns the amount of this donation. + * + * @return the donation amount as a {@link BigDecimal} + */ public BigDecimal getAmount() { return amount; } + /** + * Returns the date and time when this donation was created. + * + * @return the donation timestamp as a {@link LocalDateTime} + */ public LocalDateTime getDateTime() { return dateTime; } + /** + * Returns the user who made this donation. + * + * @return the {@link User} associated with this donation + */ public User getUser() { return user; } + /** + * Returns the organization this donation was made to. + * + * @return the {@link Organization} associated with this donation + */ public Organization getOrganization() { return organization; } diff --git a/src/main/java/domain/organization/Organization.java b/src/main/java/domain/organization/Organization.java index fd2cabf..f0a470a 100644 --- a/src/main/java/domain/organization/Organization.java +++ b/src/main/java/domain/organization/Organization.java @@ -6,39 +6,42 @@ import java.util.Objects; +/** + * Class + */ @JsonIgnoreProperties(ignoreUnknown = true) public class Organization { + + @JsonProperty("org_number") + private String orgNumber; - @JsonProperty("org_number") - private String orgNumber; + @JsonProperty("name") + private String name; - @JsonProperty("name") - private String name; + @JsonProperty("status") + private String status; - @JsonProperty("status") - private String status; + @JsonProperty("url") + private String url; - @JsonProperty("url") - private String url; + @JsonProperty("is_pre_approved") + private boolean isPreApproved; - @JsonProperty("is_pre_approved") - private boolean isPreApproved; + public Organization() {} - public Organization() {} + public String getOrgNumber() { return orgNumber; } + public void setOrgNumber(String orgNumber) { this.orgNumber = orgNumber; } - public String getOrgNumber() { return orgNumber; } - public void setOrgNumber(String orgNumber) { this.orgNumber = orgNumber; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } - public String getName() { return name; } - public void setName(String name) { this.name = name; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } - public String getStatus() { return status; } - public void setStatus(String status) { this.status = status; } + public String getUrl() { return url; } + public void setUrl(String url) { this.url = url; } - public String getUrl() { return url; } - public void setUrl(String url) { this.url = url; } - - public boolean isPreApproved() { return isPreApproved; } - public void setPreApproved(boolean preApproved) { isPreApproved = preApproved; } + public boolean isPreApproved() { return isPreApproved; } + public void setPreApproved(boolean preApproved) { isPreApproved = preApproved; } } diff --git a/src/main/java/domain/user/Role.java b/src/main/java/domain/user/Role.java index 1eba6cd..673390f 100644 --- a/src/main/java/domain/user/Role.java +++ b/src/main/java/domain/user/Role.java @@ -1,4 +1,7 @@ package domain.user; +/** + * Enum class that defines if a user has the role Admin or user. + */ public enum Role {ADMIN, USER } diff --git a/src/main/java/domain/user/User.java b/src/main/java/domain/user/User.java index ee6a2a2..8108d5a 100644 --- a/src/main/java/domain/user/User.java +++ b/src/main/java/domain/user/User.java @@ -1,5 +1,9 @@ package domain.user; +/** + * Class that defines what a User is. + * Contains get and set methods for each parameter, and validating methods. + */ public class User { private String userName; private String phoneNumber; @@ -7,16 +11,20 @@ public class User { private String password; private Long id; + /** + * Constructor that takes in the parameters that define a user. + * It contains input validation on all parameters. + * @param userName + * @param phoneNumber + * @param password + * @param eMail + */ public User(String userName, String phoneNumber, String password, String eMail) { if (userName == null || userName.isBlank()) { throw new IllegalArgumentException("Username has to be filled in"); } - if (eMail == null || eMail.isBlank()) { - throw new IllegalArgumentException("E-mail has to be filled in"); - } - if (phoneNumber == null || phoneNumber.isBlank()) { throw new IllegalArgumentException("Phonenumber has to be filled in"); } @@ -25,45 +33,102 @@ public User(String userName, String phoneNumber, String password, String eMail) throw new IllegalArgumentException("Fill in a phonenumber with 8 digits"); } + if (!isValidEmail(eMail)) { + throw new IllegalArgumentException("Invalid email address: " + eMail); + } + this.userName = userName; this.phoneNumber = phoneNumber; this.eMail = eMail.trim(); this.password = password; - this.id = id; } + /** + * Returns the username of this user. + * + * @return the username + */ public String getUsername() { return userName; } + /** + * Returns the phone number of this user. + * + * @return the phone number + */ public String getPhoneNumber() { return phoneNumber; } + /** + * Returns the email address of this user. + * + * @return the email address + */ public String getEMail() { return eMail; } + /** + * Returns the password of this user. + * + * @return the password hash + */ public String getPassword() { return password; } + /** + * Returns the ID of this user. + * + * @return the user ID + */ public long getID() { return id; } + /** + * Sets a new password hash for this user. + * + * @param password the new password hash; must not be null + */ public void setPassword(String password) { this.password = password; } + /** + * Sets a new username for this user. + * + * @param userName the new username; must not be null or blank + */ public void setUsername(String userName) { this.userName = userName; } + /** + * Sets a new phone number for this user. + * + * @param phoneNumber the new phone number; must be exactly 8 digits + */ public void setPhonenumber (String phoneNumber) { this.phoneNumber = phoneNumber; } + /** + * Sets the ID for this user. + * + * @param id the ID to assign + */ public void setId(Long id) { - if (this.id != null) throw new IllegalStateException("ID already set"); this.id = id; } + + /** + * Method that validates that a e-mail is in the right format. + * @param email + * @return + */ + private static boolean isValidEmail(String email) { + if (email == null || email.isBlank()) return false; + return email.matches("^[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}$"); + } } \ No newline at end of file diff --git a/src/main/java/integration/InnsamlingskontrollenClient.java b/src/main/java/integration/InnsamlingskontrollenClient.java index c0786fa..1f97c5b 100644 --- a/src/main/java/integration/InnsamlingskontrollenClient.java +++ b/src/main/java/integration/InnsamlingskontrollenClient.java @@ -8,6 +8,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import domain.organization.Organization; +/** + * The InnsamlingskontrollenClient class provides functionality to fetch a list + * of organizations from the Innsamlingskontrollen API. The fetched data is + * represented as an array of Organization objects. + */ public class InnsamlingskontrollenClient { public static Organization[] fetchOrganizations() throws Exception { diff --git a/src/main/java/integration/security/Sha256PasswordHasher.java b/src/main/java/integration/security/Sha256PasswordHasher.java index 2e59caa..d6c4a4f 100644 --- a/src/main/java/integration/security/Sha256PasswordHasher.java +++ b/src/main/java/integration/security/Sha256PasswordHasher.java @@ -4,9 +4,21 @@ import java.security.MessageDigest; import java.nio.charset.StandardCharsets; +/** + * Class that takes in the chosen password of a user, and hashes it. + */ public class Sha256PasswordHasher implements PasswordHasher { - @Override + /** + * Method for hashing an inputted password. + * Uses the SHA-256 algorithm. + * Transforms the passoword to a UTF-8 byte-array, then digests it using SHA-256. + * The bytes are then formatted as a 64-character string. + * @param password + * @return a 64-character hash of the password. + * @throws RuntimeException if the hashing fails. + */ + @Override public String hash(String password) { try { MessageDigest md = MessageDigest.getInstance("SHA-256"); diff --git a/src/main/java/persistence/DonationDao.java b/src/main/java/persistence/DonationDao.java index 0361f2b..8efc35d 100644 --- a/src/main/java/persistence/DonationDao.java +++ b/src/main/java/persistence/DonationDao.java @@ -66,7 +66,6 @@ private Donation mapRow(ResultSet rs) throws SQLException { rs.getString("password_hash"), rs.getString("e_mail") ); - user.setId(rs.getLong("user_id")); Organization org = new Organization(); org.setOrgNumber(rs.getString("org_number")); @@ -158,12 +157,13 @@ public List getUserDonations(long userId) { */ public List getFavoriteOrganizations(long userId) { String sql = """ - SELECT organization_id, COUNT(*) AS donation_count - FROM donation - WHERE user_Id = ? - GROUP BY organization_id + SELECT o.name, COUNT(*) AS donation_count + FROM donation d + JOIN organization o ON d.organization_id = o.org_number + WHERE d.user_id = ? + GROUP BY d.organization_id ORDER BY donation_count DESC - LIMIT 3 + LIMIT 1 """; try (Connection conn = Database.getConnection()) { @@ -269,7 +269,7 @@ private String mapRowSimple(ResultSet rs) throws SQLException { * @throws SQLException if a database access error occurs or a column label cannot be found */ private String mapRowFavorite(ResultSet rs) throws SQLException { - return rs.getString("organization_id") + return rs.getString("name") + " (" + rs.getInt("donation_count") + " donasjoner)"; } diff --git a/src/main/java/persistence/OrganizationDao.java b/src/main/java/persistence/OrganizationDao.java index a0d1346..470d74a 100644 --- a/src/main/java/persistence/OrganizationDao.java +++ b/src/main/java/persistence/OrganizationDao.java @@ -6,17 +6,29 @@ import java.util.ArrayList; import java.util.List; +/** + * Data Access Object for organization-related database operations. + * Provides methods for inserting and retrieving {@link Organization} records + * from the SQLite database. + */ public class OrganizationDao { private static final String URL = "jdbc:sqlite:givehope.db"; + /** + * Inserts a new organization into the database. + * Uses {@code INSERT OR IGNORE} so duplicate org numbers are silently skipped. + * + * @param org the {@link Organization} to insert; must not be null and must have a valid org number + * @throws SQLException if a database error occurs during the insert + */ public void insert(Organization org) throws SQLException { String sql = """ INSERT OR IGNORE INTO organization (org_number, name, status, url, is_pre_approved) VALUES (?, ?, ?, ?, ?) """; - + try (Connection conn = DriverManager.getConnection(URL); PreparedStatement stmt = conn.prepareStatement(sql)) { @@ -29,6 +41,12 @@ public void insert(Organization org) throws SQLException { } } + /** + * Retrieves all organizations stored in the database. + * + * @return a list of all {@link Organization} objects; empty list if none exist + * @throws SQLException if a database error occurs during the query + */ public List getAll() throws SQLException { String sql = "SELECT * FROM organization"; List list = new ArrayList<>(); @@ -49,4 +67,4 @@ public List getAll() throws SQLException { } return list; } -} \ No newline at end of file +} diff --git a/src/main/java/persistence/UserDao.java b/src/main/java/persistence/UserDao.java index 2fa7765..bdfc9f3 100644 --- a/src/main/java/persistence/UserDao.java +++ b/src/main/java/persistence/UserDao.java @@ -9,9 +9,20 @@ import java.sql.SQLException; import domain.user.User; +/** + * Data Access Object for user-related database operations. + * Implements {@link UserRepository} and provides methods for inserting users + * and looking them up by email, username, or either. + */ public class UserDao implements UserRepository { - + /** + * Inserts a new user into the database. + * Upon successful insertion, the generated database ID is set on the user object. + * + * @param user the {@link User} to insert; must not be null and must have a unique email and username + * @throws RuntimeException if a database error occurs, including unique constraint violations + */ public void insert(User user) { String sql = "INSERT INTO user(user_name, phone_number, e_mail, password_hash) VALUES(?, ?, ?, ?)"; @@ -39,6 +50,13 @@ public void insert(User user) { //private User mapRow(ResultSet rs) throws SQLException {} + /** + * Finds a user by their email address. + * + * @param email the email address to search for; leading/trailing whitespace is trimmed + * @return an {@link Optional} containing the user if found, or empty if no match exists + * @throws RuntimeException if a database error occurs during the query + */ @Override public Optional findByEmail(String email) { String sql = """ @@ -67,6 +85,13 @@ public Optional findByEmail(String email) { } } + /** + * Finds a user by their username. + * + * @param username the username to search for + * @return an {@link Optional} containing the user if found, or empty if no match exists + * @throws RuntimeException if a database error occurs during the query + */ @Override public Optional findByUsername(String username) { String sql = """ @@ -95,6 +120,13 @@ public Optional findByUsername(String username) { } } + /** + * Finds a user by either email or username. + * First attempts to match by email, then falls back to username if no email match is found. + * + * @param login the email or username to search for; returns empty if null or blank + * @return an {@link Optional} containing the user if found, or empty if no match exists + */ @Override public Optional findByLogin(String login) { if (login == null || login.isBlank()) { @@ -110,6 +142,13 @@ public Optional findByLogin(String login) { } + /** + * Checks whether a user with the given email address already exists in the database. + * + * @param email the email address to check + * @return {@code true} if a user with the given email exists, {@code false} otherwise + * @throws RuntimeException if a database error occurs during the query + */ @Override public boolean existsByEmail(String email) { String sql = "SELECT 1 FROM user WHERE e_mail = ?"; @@ -128,6 +167,13 @@ public boolean existsByEmail(String email) { } } + /** + * Checks whether a user with the given username already exists in the database. + * + * @param username the username to check + * @return {@code true} if a user with the given username exists, {@code false} otherwise + * @throws RuntimeException if a database error occurs during the query + */ @Override public boolean existsByUsername(String username) { String sql = "SELECT 1 FROM user WHERE user_name = ?"; diff --git a/src/main/java/persistence/UserRepository.java b/src/main/java/persistence/UserRepository.java index 3aa8d43..9b92669 100644 --- a/src/main/java/persistence/UserRepository.java +++ b/src/main/java/persistence/UserRepository.java @@ -4,6 +4,11 @@ import java.util.Optional; +/** + * Interface that defines a "contract" for which methods the class that implements it has + * to contiain. + * Is created for UserDao class, to define which methods needs to be implemented. + */ public interface UserRepository { Optional findByUsername(String username); Optional findByEmail(String email); diff --git a/src/main/java/persistence/db/Database.java b/src/main/java/persistence/db/Database.java index d4b51f2..b110a70 100644 --- a/src/main/java/persistence/db/Database.java +++ b/src/main/java/persistence/db/Database.java @@ -8,6 +8,9 @@ import java.sql.SQLException; import java.sql.Statement; +/** + * Class that initializes and connects to the generated database. + */ public final class Database { public static final String URL = "jdbc:sqlite:givehope.db"; @@ -15,6 +18,13 @@ public final class Database { initializeDatabase(); } + /** + * Methods that provides a connection the URL/file that is initialized. + * Runs "PRAGMA FOREIGN KEYS = ON", which turns foreign key check on in SQLite. + * Connection class is used connects the java-code to the database/SQL-code. + * @return returns connection to database. + * @throws SQLException + */ public static Connection getConnection() throws SQLException { Connection conn = DriverManager.getConnection(URL); try (Statement stmt = conn.createStatement()) { @@ -23,7 +33,13 @@ public static Connection getConnection() throws SQLException { return conn; } - private static void initializeDatabase() { + /** + * Method that initializes/creates the database. + * Uses file "schema.sql" to create, which contains SQL-code that creates the tables. + * Splits string at ";", to separate each CREATE TABLE-sentences. + * @throws RuntimeException if the schema file cannot be read or a database error occurs. + */ + public static void initializeDatabase() { try (Connection conn = DriverManager.getConnection(URL); Statement stmt = conn.createStatement()) { diff --git a/src/main/java/ui/GiveHopeGIU.java b/src/main/java/ui/GiveHopeGIU.java deleted file mode 100644 index 2617122..0000000 --- a/src/main/java/ui/GiveHopeGIU.java +++ /dev/null @@ -1,23 +0,0 @@ -package ui; - -import javafx.application.Application; -import javafx.fxml.FXMLLoader; -import javafx.scene.Scene; -import javafx.stage.Stage; - -public class GiveHopeGIU extends Application { - - @Override - public void start(Stage stage) throws Exception { - FXMLLoader loader = new FXMLLoader(getClass().getResource("/ui/home.fxml")); - Scene scene = new Scene(loader.load(), 600, 400); - - stage.setTitle("GiveHope"); - stage.setScene(scene); - stage.show(); - } - - public static void main(String[] args) { - launch(); - } -} \ No newline at end of file diff --git a/src/main/java/ui/Page.java b/src/main/java/ui/Page.java new file mode 100644 index 0000000..c788a50 --- /dev/null +++ b/src/main/java/ui/Page.java @@ -0,0 +1,25 @@ +package ui; + +/** + * Represents the different pages available in the application, where each page + * is associated with a specific FXML file used for its layout. + */ +public enum Page { + HOME("Home.fxml"), + PROFILE("MyProfile.fxml"), + SIGNIN("SignIn.fxml"), + REGISTER("Register.fxml"), + ORGANIZATIONS("OrganizationsView.fxml"), + MYPROFILE("MyProfile.fxml"); + + + private final String fileName; + + Page(String fileName) { + this.fileName = fileName; + } + + public String getFileName() { + return fileName; + } +} diff --git a/src/main/java/ui/controller/HomeController.java b/src/main/java/ui/controller/HomeController.java index bda7d61..8aafbd2 100644 --- a/src/main/java/ui/controller/HomeController.java +++ b/src/main/java/ui/controller/HomeController.java @@ -1,24 +1,34 @@ package ui.controller; +import application.security.PasswordHasher; +import application.user.UserSignIn; +import integration.security.Sha256PasswordHasher; import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.scene.Parent; import javafx.scene.control.Button; +import persistence.UserDao; +import ui.Page; import java.io.IOException; +import java.util.function.Consumer; -public class HomeController { +public class HomeController implements NavigationAware { + + private Consumer onNavigate; + + public void setOnNavigate(Consumer onNavigate) { + this.onNavigate = onNavigate; + } public Button signInButton; @FXML - private void goToSignIn() throws IOException { - Parent root = FXMLLoader.load(getClass().getResource("/ui/signIn.fxml")); - signInButton.getScene().setRoot(root); + private void handleSignIn() throws IOException { + onNavigate.accept(Page.SIGNIN); } @FXML - private void handleBrowse() { - System.out.println("Browse button pressed"); + private void handleBrowseOrganizations() { + onNavigate.accept(Page.ORGANIZATIONS); } + } \ No newline at end of file diff --git a/src/main/java/ui/controller/MainController.java b/src/main/java/ui/controller/MainController.java new file mode 100644 index 0000000..1586f28 --- /dev/null +++ b/src/main/java/ui/controller/MainController.java @@ -0,0 +1,63 @@ +package ui.controller; + +import application.user.UserRegister; +import application.user.UserSignIn; +import integration.security.Sha256PasswordHasher; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.layout.BorderPane; +import persistence.UserDao; +import ui.Page; + +import java.io.IOException; + +public class MainController { + + @FXML + private BorderPane mainPane; + + @FXML + private NavbarController navbarController; + + public void initialize() { + try { + navbarController.setOnNavigate(page -> { + try { + loadPage(page); + } catch (IOException e) { + + } + }); + loadPage(Page.HOME); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void loadPage(Page page) throws IOException { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/view/" + page.getFileName())); + Parent root = loader.load(); + + Object controller = loader.getController(); + if (controller instanceof NavigationAware navigationAware) { + navigationAware.setOnNavigate(p -> { + try { + loadPage(p); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + if (controller instanceof SignInController c) { + c.setUserSignIn(new UserSignIn(new Sha256PasswordHasher(), new UserDao())); + } + if (controller instanceof RegisterController c) { + c.setUserRegister(new UserRegister(new Sha256PasswordHasher(), new UserDao())); + } + + mainPane.setCenter(root); + navbarController.setActivePage(page); + navbarController.updateAuthButton(); + } +} diff --git a/src/main/java/ui/controller/MyProfileController.java b/src/main/java/ui/controller/MyProfileController.java new file mode 100644 index 0000000..f6b82ff --- /dev/null +++ b/src/main/java/ui/controller/MyProfileController.java @@ -0,0 +1,73 @@ +package ui.controller; + +import application.user.UserStatistics; +import domain.donation.Donation; +import domain.user.User; +import java.util.List; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import persistence.DonationDao; +import ui.Page; +import util.SessionManager; + +import java.util.function.Consumer; + +public class MyProfileController implements NavigationAware { + + private Consumer onNavigate; + @FXML private Label totalDonatedLabel; + @FXML private Label numberOfDonationsLabel; + @FXML private Label favoriteOrgLabel; + @FXML private Label nameLabel; + @FXML private Label emailLabel; + @FXML private Label phoneLabel; + @FXML private TableView donationTable; + @FXML private TableColumn orgColumn; + @FXML private TableColumn amountColumn; + @FXML private TableColumn dateColumn; + + public void setOnNavigate(Consumer onNavigate) { + this.onNavigate = onNavigate; + } + + @FXML + public void initialize() { + User user = SessionManager.getCurrentUser(); + if (user == null) return; + + nameLabel.setText("Name: " + user.getUsername()); + emailLabel.setText("E-mail: " + user.getEMail()); + phoneLabel.setText("Phonenumber: " + user.getPhoneNumber()); + + UserStatistics stats = new UserStatistics(); + totalDonatedLabel.setText(stats.userTotalDonationAmount(user) + " kr"); + numberOfDonationsLabel.setText(stats.getTotalDonationsMade(user)); + + List favOrg = stats.userFavoriteOrganization(user); + favoriteOrgLabel.setText(favOrg.isEmpty() ? "None" : favOrg.get(0)); + + orgColumn.setCellValueFactory(cell -> + new SimpleStringProperty(cell.getValue().getOrganization().getName())); + amountColumn.setCellValueFactory(cell -> + new SimpleStringProperty(cell.getValue().getAmount().toString() + " kr")); + dateColumn.setCellValueFactory(cell -> + new SimpleStringProperty(cell.getValue().getDateTime().toLocalDate().toString())); + + DonationDao donationDao = new DonationDao(); + ObservableList data = FXCollections.observableArrayList( + donationDao.findByUser(user.getID()) + ); + donationTable.setItems(data); + } + + @FXML + private void handleSignOut() { + SessionManager.signOut(); + onNavigate.accept(Page.HOME); + } +} diff --git a/src/main/java/ui/controller/NavbarController.java b/src/main/java/ui/controller/NavbarController.java new file mode 100644 index 0000000..24e7aac --- /dev/null +++ b/src/main/java/ui/controller/NavbarController.java @@ -0,0 +1,68 @@ +package ui.controller; + +import java.util.function.Consumer; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import ui.Page; +import util.SessionManager; + +public class NavbarController { + + private Consumer onNavigate; + @FXML + private Button homeBtn; + @FXML + private Button orgBtn; + @FXML + private Button signInBtn; + + public void setOnNavigate(Consumer onNavigate) { + this.onNavigate = onNavigate; + } + + public void setActivePage(Page page) { + homeBtn.getStyleClass().remove("nav-button-active"); + orgBtn.getStyleClass().remove("nav-button-active"); + signInBtn.getStyleClass().remove("nav-button-active"); + + Button buttonForPage = switch (page) { + case HOME -> homeBtn; + case ORGANIZATIONS -> orgBtn; + case SIGNIN, PROFILE -> signInBtn; + default -> null; + }; + + if (buttonForPage != null) { + buttonForPage.getStyleClass().add("nav-button-active"); + } + } + + public void goHome() { + onNavigate.accept(Page.HOME); + } + + public void goToSignIn() { + if (SessionManager.isSignedIn()) { + onNavigate.accept(Page.PROFILE); + } else { + onNavigate.accept(Page.SIGNIN); + } + } + + public void goToOrganizations() { + onNavigate.accept(Page.ORGANIZATIONS); + } + + public void goToMyProfile() { + onNavigate.accept(Page.PROFILE); + } + + public void updateAuthButton() { + if (SessionManager.isSignedIn()) { + signInBtn.setText("My Profile"); + } else { + signInBtn.setText("Sign In"); + } + } +} diff --git a/src/main/java/ui/controller/NavigationAware.java b/src/main/java/ui/controller/NavigationAware.java new file mode 100644 index 0000000..82f215f --- /dev/null +++ b/src/main/java/ui/controller/NavigationAware.java @@ -0,0 +1,20 @@ +package ui.controller; + +import ui.Page; + +import java.util.function.Consumer; + +/** + * Interface for controllers that need to navigate between pages. + * Controllers that implement this interface can switch to a different page + * by calling the navigation function they receive. + */ +public interface NavigationAware { + + /** + * Sets the navigation function that the controller can use to switch pages. + * + * @param onNavigate a function that takes a {@link Page} and navigates to it + */ + void setOnNavigate(Consumer onNavigate); +} \ No newline at end of file diff --git a/src/main/java/ui/controller/OrganizationController.java b/src/main/java/ui/controller/OrganizationController.java new file mode 100644 index 0000000..547c746 --- /dev/null +++ b/src/main/java/ui/controller/OrganizationController.java @@ -0,0 +1,150 @@ +package ui.controller; + +import application.security.PasswordHasher; +import application.user.UserSignIn; +import domain.organization.Organization; + +import integration.security.Sha256PasswordHasher; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.VBox; +import persistence.OrganizationDao; +import persistence.UserDao; +import ui.Page; +import util.SessionManager; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class OrganizationController { + + @FXML + private TextField searchField; + @FXML + private FlowPane organizationContainer; + @FXML + private Button previousPageBtn; + @FXML + private Button nextPageBtn; + @FXML + private Label pageLabel; + @FXML + private Button signInAndMyProfileBtn; + + private List allOrgs; + private List filtrertOrgs; + private int page = 0; + private final int PER_PAGE = 15; + + private Consumer onNavigate; + + public void setOnNavigate(Consumer onNavigate) { + this.onNavigate = onNavigate; + } + + public void initialize() { + try { + OrganizationDao dao = new OrganizationDao(); + allOrgs = dao.getAll(); + System.out.println("Fetched: " + allOrgs.size() + " organizations"); + filtrertOrgs = new ArrayList<>(allOrgs); + showPage(); + + searchField.textProperty().addListener((obs, old, nyTekst) -> { + filtrertOrgs = allOrgs.stream() + .filter(org -> org.getName().toLowerCase() + .contains(nyTekst.toLowerCase())) + .collect(Collectors.toList()); + page = 0; + showPage(); + }); + + organizationContainer.widthProperty().addListener((obs, oldWidth, newWidth) -> { + double cardWidth = (newWidth.doubleValue() - 60) / 3; + for (var node : organizationContainer.getChildren()) { + if (node instanceof VBox card) { + card.setPrefWidth(cardWidth); + } + } + }); + + } catch (SQLException e) { + System.out.println("Feil ved henting: " + e.getMessage()); + } + } + + private void showPage() { + if (filtrertOrgs == null || filtrertOrgs.isEmpty()) { + System.out.println("No organizations to show!"); + return; + } + + organizationContainer.getChildren().clear(); + + int fra = page * PER_PAGE; + int til = Math.min(fra + PER_PAGE, filtrertOrgs.size()); + + for (int i = fra; i < til; i++) { + organizationContainer.getChildren().add(createCard(filtrertOrgs.get(i))); + } + + int totalSider = (int) Math.ceil((double) filtrertOrgs.size() / PER_PAGE); + pageLabel.setText("Side " + (page + 1) + " av " + totalSider); + previousPageBtn.setDisable(page == 0); + nextPageBtn.setDisable(page >= totalSider - 1); + } + + private VBox createCard(Organization org) { + VBox card = new VBox(8); + card.getStyleClass().add("org-card"); + double width = organizationContainer.getWidth(); + card.setPrefWidth(width > 0 ? (width - 60) / 3 : 400); + + Label name = new Label(org.getName()); + name.getStyleClass().add("org-name"); + name.setWrapText(true); + + Label orgnr = new Label("Org.nr: " + org.getOrgNumber()); + orgnr.getStyleClass().add("org-detail"); + + Label status = new Label(org.isPreApproved() ? "✓ Forhåndsgodkjent" : "✓ Verified"); + status.getStyleClass().add("org-status"); + + Button donasjonBtn = new Button("Donate"); + donasjonBtn.getStyleClass().add("donate-button"); + donasjonBtn.setMaxWidth(Double.MAX_VALUE); + + card.getChildren().addAll(name, orgnr, status, donasjonBtn); + return card; + } + + @FXML + private void previousPage() { + page--; + showPage(); + } + + @FXML + private void nextPage() { + page++; + showPage(); + } + + @FXML + private void handleGoHome() { + onNavigate.accept(Page.HOME); + } + + @FXML + private void handleSignIn() { + onNavigate.accept(Page.SIGNIN); + } + +} diff --git a/src/main/java/ui/controller/RegisterController.java b/src/main/java/ui/controller/RegisterController.java new file mode 100644 index 0000000..9db9bb4 --- /dev/null +++ b/src/main/java/ui/controller/RegisterController.java @@ -0,0 +1,74 @@ +package ui.controller; + +import application.security.PasswordHasher; +import application.user.UserRegister; +import application.user.UserSignIn; +import integration.security.Sha256PasswordHasher; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; +import persistence.UserDao; +import ui.Page; + +import java.io.IOException; +import java.util.function.Consumer; + +public class RegisterController implements NavigationAware { + + @FXML private TextField usernameField; + @FXML private TextField emailField; + @FXML private TextField phoneNumberField; + @FXML private PasswordField passwordField; + @FXML private PasswordField confirmPasswordField; + @FXML private Label errorLabel; + + private UserRegister userRegister; + private Consumer onNavigate; + + public void setUserRegister(UserRegister userRegister) { + this.userRegister = userRegister; + } + + public void setOnNavigate(Consumer onNavigate) { + this.onNavigate = onNavigate; + } + + @FXML + private void handleRegister() { + String username = usernameField.getText().trim(); + String email = emailField.getText().trim(); + String phoneNr = phoneNumberField.getText().trim(); + String password = passwordField.getText().trim(); + String confirmPassword = confirmPasswordField.getText().trim(); + + if (username.isEmpty() || email.isEmpty() || phoneNr.isEmpty() || password.isEmpty() || confirmPassword.isEmpty()) { + errorLabel.setText("Please fill in all fields."); + return; + } + + if (!password.equals(confirmPassword)) { + errorLabel.setText("Passwords do not match."); + return; + } + + errorLabel.setText(""); + + try { + userRegister.execute(username, phoneNr, password, email); + onNavigate.accept(Page.SIGNIN); + } catch (IllegalArgumentException e) { + errorLabel.setText(e.getMessage()); + } + } + + @FXML + private void handleSignIn() { + onNavigate.accept(Page.SIGNIN); + } + + @FXML + private void handleGoHome() { + onNavigate.accept(Page.HOME); + } +} diff --git a/src/main/java/ui/controller/SignInController.java b/src/main/java/ui/controller/SignInController.java index 3e141bf..6541460 100644 --- a/src/main/java/ui/controller/SignInController.java +++ b/src/main/java/ui/controller/SignInController.java @@ -1,36 +1,68 @@ package ui.controller; +import application.security.PasswordHasher; +import application.user.UserRegister; +import application.user.UserSignIn; +import domain.user.User; +import integration.security.Sha256PasswordHasher; +import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; +import javafx.stage.Stage; +import persistence.UserDao; +import ui.Page; +import util.SessionManager; -public class SignInController { +import java.io.IOException; +import java.util.function.Consumer; - @FXML private TextField emailField; +public class SignInController implements NavigationAware { + + @FXML private TextField loginField; @FXML private PasswordField passwordField; @FXML private Label errorLabel; + private UserSignIn userSignIn; + private Consumer onNavigate; + + public void setUserSignIn(UserSignIn userSignIn) { + this.userSignIn = userSignIn; + } + + public void setOnNavigate(Consumer onNavigate) { + this.onNavigate = onNavigate; + } + @FXML private void handleSignIn() { - String email = emailField.getText(); - String password = passwordField.getText(); - // Mini-sjekk for å se at wiring funker - if (email == null || email.isBlank() || password == null || password.isBlank()) { + String login = loginField.getText().trim(); + String password = passwordField.getText().trim(); + + if (login.isEmpty() || password.isEmpty()) { errorLabel.setText("Please enter email and password."); return; } - errorLabel.setText(""); // nullstill - System.out.println("Trying to sign in with: " + email); + // nullstill eventuell gammel feilmelding + errorLabel.setText(""); - // Her skal du senere kalle service/DB (ikke her inne direkte!) + try { + User user = userSignIn.execute(login, password); + SessionManager.signIn(user); + onNavigate.accept(Page.HOME); + } catch (IllegalArgumentException e) { + errorLabel.setText(e.getMessage()); + } } @FXML - private void goToRegister() { - System.out.println("Go to Register clicked"); - // Neste steg: bytte scene/view + private void handleRegister() throws IOException { + onNavigate.accept(Page.REGISTER); } } \ No newline at end of file diff --git a/src/main/java/util/SessionManager.java b/src/main/java/util/SessionManager.java new file mode 100644 index 0000000..3cbf39c --- /dev/null +++ b/src/main/java/util/SessionManager.java @@ -0,0 +1,47 @@ +package util; + +import domain.user.User; + +/** + * Class that manages current user session for the application. + * Has methods that keep track of if a user is signed in or not. + * Only one user can be signed in at a time. + */ +public class SessionManager { + private static boolean SignedIn = false; + private static User currentUser = null; + + /** + * Signs in a user. + * Stores the user as the current session holder. + * @param the user tom sign in. + */ + public static void signIn(User user) { + SignedIn = true; + currentUser = user; + } + + /** + * Signs out current user by clearing session. + */ + public static void signOut() { + SignedIn = false; + currentUser = null; + } + + /** + * Method returns boolean value on whether a user is signed in. + * @return false if user is not signed in, true if signed in. + */ + public static boolean isSignedIn() { + return SignedIn; + } + + /** + * Method that returns the current user that the session contains. + * @return the current user + */ + public static User getCurrentUser() { + return currentUser; + } +} diff --git a/src/main/resources/css/Global.css b/src/main/resources/css/Global.css new file mode 100644 index 0000000..5de6600 --- /dev/null +++ b/src/main/resources/css/Global.css @@ -0,0 +1,129 @@ +.root { + -fx-background-color: #f5f5f5; + -fx-font-family: "Segoe UI"; +} + + +.nav-button:hover { + -fx-background-color: #bfc3c7; + -fx-scale-x: 1.05; + -fx-scale-y: 1.05; +} +.nav-button:pressed { + -fx-background-color: #a5a8ab; + -fx-scale-x: 0.97; + -fx-scale-y: 0.97; +} + +.page-button { + -fx-background-color: #d9d9d9; + -fx-background-radius: 50; + -fx-font-size: 16px; + -fx-cursor: hand; + -fx-padding: 8px 20px; +} + +.page-button:hover { + -fx-background-color: #bfc3c7; + -fx-scale-x: 1.05; + -fx-scale-y: 1.05; +} + +.page-button:pressed { + -fx-background-color: #a5a8ab; + -fx-scale-x: 0.97; + -fx-scale-y: 0.97; +} + +.page-button:disabled { + -fx-background-color: #eeeeee; + -fx-text-fill: #aaaaaa; + -fx-cursor: default; +} + +.page-label { + -fx-font-size: 14px; + -fx-text-fill: #555555; + -fx-padding: 8px 15px; +} + + +.page-title { + -fx-font-size: 36px; + -fx-font-weight: bold; + -fx-text-fill: #1a1a1a; +} + +.scroll-pane { + -fx-background-color: transparent; + -fx-border-color: transparent; + -fx-border-width: 0; + -fx-padding: 0; +} + +.scroll-pane > .viewport { + -fx-background-color: transparent; +} + +.scroll-bar:vertical { + -fx-background-color: transparent; + -fx-pref-width: 10px; +} + +.scroll-bar:vertical .thumb { + -fx-background-color: #aaaaaa; + -fx-background-radius: 4px; +} + +.scroll-bar:vertical .thumb:hover { + -fx-background-color: #888888; + -fx-cursor: hand; +} + +.scroll-bar:vertical .track { + -fx-background-color: transparent; +} + +.scroll-bar .increment-button, +.scroll-bar .decrement-button { + -fx-pref-height: 0; + -fx-pref-width: 0; + -fx-opacity: 0; +} + +.section-title { + -fx-font-size: 22px; + -fx-text-fill: #1a1a1a; +} + + +/*NavigationBar*/ +.navbar { + -fx-background-color: #1e3a8a; + -fx-padding: 12 20 12 20; +} + +.nav-button { + -fx-background-color: transparent; + -fx-border-color: rgba(255,255,255,0.4); + -fx-border-radius: 20; + -fx-background-radius: 20; + -fx-text-fill: white; + -fx-font-size: 13px; + -fx-cursor: hand; + -fx-padding: 6 16 6 16; +} + +.nav-button-active { + -fx-background-color: white; + -fx-text-fill: #1a1a2e; + -fx-font-weight: bold; +} + +.logo-label { + -fx-font-size: 22px; + -fx-font-family: "Times New Roman"; + -fx-text-fill: white; + -fx-font-weight: bold; +} + diff --git a/src/main/resources/css/Home.css b/src/main/resources/css/Home.css new file mode 100644 index 0000000..616c23c --- /dev/null +++ b/src/main/resources/css/Home.css @@ -0,0 +1,121 @@ +.hero-section { + -fx-background-color: #1e3a8a; + -fx-padding: 56 48 48 48; +} + +.hero-tag { + -fx-background-color: rgba(255,255,255,0.15); + -fx-text-fill: white; + -fx-font-size: 12px; + -fx-background-radius: 20; + -fx-padding: 4 12 4 12; +} + +.hero-title { + -fx-font-size: 28px; + -fx-text-fill: white; + -fx-font-weight: bold; +} + +.hero-subtitle { + -fx-font-size: 16px; + -fx-text-fill: white; + -fx-wrap-text: true; + -fx-max-width: 440; +} + +.hero-button-primary { + -fx-background-color: white; + -fx-text-fill: #1e3a8a; + -fx-font-weight: bold; + -fx-background-radius: 6; + -fx-padding: 10 22 10 22; + -fx-cursor: hand; +} + +.hero-button-secondary { + -fx-background-color: transparent; + -fx-text-fill: white; + -fx-border-color: rgba(255,255,255,0.6); + -fx-border-radius: 6; + -fx-background-radius: 6; + -fx-padding: 10 22 10 22; + -fx-cursor: hand; +} + +.hero-button-primary:hover { + -fx-background-color: #f0f0f0; + -fx-scale-x: 1.03; + -fx-scale-y: 1.03; +} + +.hero-button-primary:pressed { + -fx-background-color: #e0e0e0; + -fx-scale-x: 0.97; + -fx-scale-y: 0.97; +} + +.hero-button-secondary:hover { + -fx-background-color: rgba(255,255,255,0.15); + -fx-scale-x: 1.03; + -fx-scale-y: 1.03; +} + +.hero-button-secondary:pressed { + -fx-scale-x: 0.97; + -fx-scale-y: 0.97; +} + +.stats-stripe { + -fx-background-color: #162d6e; + -fx-padding: 20 48 20 48; +} + +.stats-number { + -fx-font-size: 24px; + -fx-font-weight: bold; + -fx-text-fill: white; +} + +.stats-label { + -fx-font-size: 14px; + -fx-text-fill: rgba(255,255,255,0.7); +} + +.features-section { + -fx-background-color: #f8faff; + -fx-padding: 32 48 48 48; +} + +.features-title { + -fx-font-size: 18px; + -fx-font-weight: bold; + -fx-text-fill: #1a1a2e; +} + +.features-grid { + -fx-spacing: 16; +} + +.feature-card { + -fx-background-color: white; + -fx-border-color: #dde3f0; + -fx-border-width: 1; + -fx-border-radius: 10; + -fx-background-radius: 10; + -fx-min-height: 120; + -fx-padding: 20; + -fx-spacing: 8; +} + +.feature-card-title { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: #1a1a2e; +} + +.feature-card-desc { + -fx-font-size: 13px; + -fx-text-fill: #666666; + -fx-wrap-text: true; +} \ No newline at end of file diff --git a/src/main/resources/css/MyProfile.css b/src/main/resources/css/MyProfile.css new file mode 100644 index 0000000..a9b04ec --- /dev/null +++ b/src/main/resources/css/MyProfile.css @@ -0,0 +1,4 @@ + +.profile-label{ + -fx-font-size: 16px; +} \ No newline at end of file diff --git a/src/main/resources/css/OrganizationView.css b/src/main/resources/css/OrganizationView.css new file mode 100644 index 0000000..6904bfd --- /dev/null +++ b/src/main/resources/css/OrganizationView.css @@ -0,0 +1,58 @@ +.org-card { + -fx-background-color: #e8e8e8; + -fx-background-radius: 12px; + -fx-padding: 20px; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.08), 8, 0, 0, 3); +} + +.org-card:hover { + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.15), 12, 0, 0, 5); + -fx-cursor: hand; +} + +.org-name { + -fx-font-size: 20px; +} + +.donate-button { + -fx-background-color: #1e3a8a; + -fx-text-fill: white; + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-background-radius: 8px; + -fx-padding: 10px; + -fx-cursor: hand; +} + +.donate-button:hover { + -fx-background-color: #2649a8; + -fx-scale-x: 1.05; + -fx-scale-y: 1.05; +} +.donate-button:pressed { + -fx-background-color: #162d6e; + -fx-scale-x: 0.97; + -fx-scale-y: 0.97; +} + + +.page-subtitle { + -fx-font-size: 14px; + -fx-text-fill: #555555; +} + + + +.search-field { + -fx-background-color: white; + -fx-border-color: #e0e0e0; + -fx-border-radius: 8px; + -fx-background-radius: 8px; + -fx-padding: 12px 15px; + -fx-font-size: 14px; + -fx-prompt-text-fill: #aaaaaa; +} + +.search-field:focused { + -fx-border-color: #3a86ff; +} \ No newline at end of file diff --git a/src/main/resources/css/Register.css b/src/main/resources/css/Register.css new file mode 100644 index 0000000..a51cd40 --- /dev/null +++ b/src/main/resources/css/Register.css @@ -0,0 +1,129 @@ +/* ===================== + GiveHope – register.css + Matches the sign-in page aesthetic + ===================== */ + +/* Root / background */ +.root-pane { + -fx-background-color: #e8e8e8; +} + +/* ── Top bar ── */ +.top-bar { + -fx-padding: 14 18 14 18; + -fx-alignment: CENTER_LEFT; + -fx-background-color: #e8e8e8; + -fx-spacing: 12; +} + +.logo-label { + -fx-font-size: 15px; + -fx-font-family: "Segoe UI", sans-serif; + -fx-text-fill: #2c2c2c; + -fx-font-weight: normal; +} + +.home-button { + -fx-background-color: #d4d4d4; + -fx-text-fill: #2c2c2c; + -fx-font-size: 14px; + -fx-font-family: "Segoe UI", sans-serif; + -fx-background-radius: 12px; + -fx-padding: 8 18 8 18; + -fx-cursor: hand; + -fx-border-color: transparent; +} + +.home-button:hover { + -fx-background-color: #c4c4c4; +} + +/* ── Form container ── */ +.form-container { + -fx-padding: 0 0 80 0; +} + +/* ── Input fields ── */ +.input-field { + -fx-background-color: #ffffff; + -fx-background-radius: 24px; + -fx-border-radius: 24px; + -fx-border-color: transparent; + -fx-padding: 14 22 14 22; + -fx-font-size: 15px; + -fx-font-family: "Segoe UI", sans-serif; + -fx-prompt-text-fill: #b0b0b0; + -fx-text-fill: #2c2c2c; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.06), 6, 0, 0, 1); + -fx-pref-height: 50px; +} + +.input-field:focused { + -fx-background-color: #ffffff; + -fx-border-color: #a8c4d4; + -fx-border-radius: 24px; + -fx-border-width: 1.5px; +} + +/* ── Primary button ── */ +.primary-button { + -fx-background-color: #a8c4d4; + -fx-text-fill: #2c2c2c; + -fx-font-size: 16px; + -fx-font-family: "Segoe UI", sans-serif; + -fx-background-radius: 24px; + -fx-padding: 14 0 14 0; + -fx-cursor: hand; + -fx-pref-height: 54px; + -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.08), 6, 0, 0, 2); +} + +.primary-button:hover { + -fx-background-color: #96b5c8; +} + +.primary-button:pressed { + -fx-background-color: #84a6bb; +} + +/* ── Footer ── */ +.footer-label { + -fx-font-size: 13px; + -fx-font-family: "Segoe UI", sans-serif; + -fx-text-fill: #888888; +} + +.footer-link { + -fx-font-size: 13px; + -fx-font-family: "Segoe UI", sans-serif; + -fx-text-fill: #5a8fa8; + -fx-border-color: transparent; + -fx-padding: 0; + -fx-underline: false; +} + +.footer-link:hover { + -fx-text-fill: #3d7290; + -fx-underline: true; +} + +.register-button{ + -fx-font-size: 24px; + -fx-background-color: #3a86ff; + -fx-text-fill: white; + -fx-font-family: "Segoe UI", sans-serif; + -fx-cursor: hand; + -fx-border-color: transparent; + -fx-background-radius: 14px; +} + +.register-button:hover { + -fx-background-color: #266ddf; + -fx-scale-x: 1.05; + -fx-scale-y: 1.05; +} +.register-button:pressed { + -fx-background-color: #1a5bbf; + -fx-scale-x: 0.97; + -fx-scale-y: 0.97; +} \ No newline at end of file diff --git a/src/main/resources/css/SignIn.css b/src/main/resources/css/SignIn.css new file mode 100644 index 0000000..36cdbb8 --- /dev/null +++ b/src/main/resources/css/SignIn.css @@ -0,0 +1,20 @@ +.signIn-button{ + -fx-font-size: 24px; + -fx-background-color: #3a86ff; + -fx-text-fill: white; + -fx-font-family: "Segoe UI", sans-serif; + -fx-cursor: hand; + -fx-border-color: transparent; + -fx-background-radius: 14px; +} + +.signIn-button:hover { + -fx-background-color: #266ddf; + -fx-scale-x: 1.05; + -fx-scale-y: 1.05; +} +.signIn-button:pressed { + -fx-background-color: #1a5bbf; + -fx-scale-x: 0.97; + -fx-scale-y: 0.97; +} \ No newline at end of file diff --git a/src/main/resources/ui/home.fxml b/src/main/resources/ui/home.fxml deleted file mode 100644 index 5028391..0000000 --- a/src/main/resources/ui/home.fxml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - + + + + + + + + + diff --git a/src/main/resources/view/Navbar.fxml b/src/main/resources/view/Navbar.fxml new file mode 100644 index 0000000..21a512e --- /dev/null +++ b/src/main/resources/view/Navbar.fxml @@ -0,0 +1,19 @@ + + + + + + + + + +