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/ui/signIn.fxml b/src/main/resources/ui/signIn.fxml
deleted file mode 100644
index 5bb59d4..0000000
--- a/src/main/resources/ui/signIn.fxml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/resources/view/Home.fxml b/src/main/resources/view/Home.fxml
new file mode 100644
index 0000000..92941f1
--- /dev/null
+++ b/src/main/resources/view/Home.fxml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainView.fxml b/src/main/resources/view/MainView.fxml
new file mode 100644
index 0000000..6b655b0
--- /dev/null
+++ b/src/main/resources/view/MainView.fxml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MyProfile.fxml b/src/main/resources/view/MyProfile.fxml
new file mode 100644
index 0000000..40ec2de
--- /dev/null
+++ b/src/main/resources/view/MyProfile.fxml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/OrganizationsView.fxml b/src/main/resources/view/OrganizationsView.fxml
new file mode 100644
index 0000000..c755d78
--- /dev/null
+++ b/src/main/resources/view/OrganizationsView.fxml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/Register.fxml b/src/main/resources/view/Register.fxml
new file mode 100644
index 0000000..4753596
--- /dev/null
+++ b/src/main/resources/view/Register.fxml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/SignIn.fxml b/src/main/resources/view/SignIn.fxml
new file mode 100644
index 0000000..fda7a96
--- /dev/null
+++ b/src/main/resources/view/SignIn.fxml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+