diff --git a/pom.xml b/pom.xml
index 0f3156b..a888bc6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,6 +51,12 @@
3.46.1.0
+
+ org.jsoup
+ jsoup
+ 1.17.2
+
+
@@ -73,6 +79,19 @@
3.5.4
+
+ org.openjfx
+ javafx-maven-plugin
+ 0.0.8
+
+ app.Main
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java
index e9947a6..fea73ff 100644
--- a/src/main/java/app/Main.java
+++ b/src/main/java/app/Main.java
@@ -1,19 +1,18 @@
package app;
import domain.organization.Organization;
+import domain.organization.OrganizationDetails;
import integration.InnsamlingskontrollenClient;
import application.security.PasswordHasher;
-import application.user.UserRegister;
import domain.donation.Donation;
import domain.user.User;
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;
@@ -30,6 +29,11 @@ public static void main(String[] args) {
public void start(Stage stage) throws Exception {
Database.initializeDatabase();
System.out.println("DB path: " + new java.io.File("givehope.db").getAbsolutePath());
+ OrganizationDetails details = InnsamlingskontrollenClient.fetchDetails(
+ "https://www.innsamlingskontrollen.no/organisasjoner/caritas-norge/"
+ );
+ System.out.println("Description: " + details.getDescription());
+ System.out.println("Logo: " + details.getLogoUrl());
OrganizationDao orgDao = new OrganizationDao();
Organization[] orgs = InnsamlingskontrollenClient.fetchOrganizations();
for (Organization org : orgs) {
diff --git a/src/main/java/domain/organization/OrganizationDetails.java b/src/main/java/domain/organization/OrganizationDetails.java
new file mode 100644
index 0000000..e892e82
--- /dev/null
+++ b/src/main/java/domain/organization/OrganizationDetails.java
@@ -0,0 +1,19 @@
+package domain.organization;
+
+public class OrganizationDetails {
+ private String description;
+ private String logoUrl;
+
+ public OrganizationDetails(String description, String logoUrl) {
+ this.description = description;
+ this.logoUrl = logoUrl;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getLogoUrl() {
+ return logoUrl;
+ }
+}
diff --git a/src/main/java/integration/InnsamlingskontrollenClient.java b/src/main/java/integration/InnsamlingskontrollenClient.java
index c0786fa..0699c7d 100644
--- a/src/main/java/integration/InnsamlingskontrollenClient.java
+++ b/src/main/java/integration/InnsamlingskontrollenClient.java
@@ -4,9 +4,14 @@
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
import com.fasterxml.jackson.databind.ObjectMapper;
import domain.organization.Organization;
+import domain.organization.OrganizationDetails;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+
public class InnsamlingskontrollenClient {
@@ -24,4 +29,40 @@ public static Organization[] fetchOrganizations() throws Exception {
return mapper.readValue(response.body(), Organization[].class);
}
+
+ public static OrganizationDetails fetchDetails(String url) {
+ try {
+ org.jsoup.Connection conn = Jsoup.connect(url);
+ conn.timeout(10000);
+ Document doc = conn.get();
+ doc.charset(StandardCharsets.UTF_8);
+ System.out.println(doc.select("section.information").text());
+
+ String description = "";
+ System.out.println("Section size: " + doc.select("section.information div > p").size());
+ for (org.jsoup.nodes.Element p : doc.select("section.information div > p")) {
+ System.out.println("P element: " + p.text());
+ if (!p.text().isBlank()) {
+ description = p.text();
+ break;
+ }
+ }
+
+ if (description.isBlank()) {
+ String sectionText = doc.select("section.information").text();
+ if (!sectionText.isBlank()) {
+ int lesmerIndex = sectionText.indexOf("Les mer");
+ description = lesmerIndex > 0
+ ? sectionText.substring(0, lesmerIndex).trim()
+ : sectionText;
+ }
+ }
+
+ String logoUrl = doc.select("img[src*=logo]").attr("src");
+
+ return new OrganizationDetails(description, logoUrl);
+ } catch (Exception e) {
+ return new OrganizationDetails("", "");
+ }
+ }
}
diff --git a/src/main/java/ui/controller/MainController.java b/src/main/java/ui/controller/MainController.java
index b7077fc..aaa182b 100644
--- a/src/main/java/ui/controller/MainController.java
+++ b/src/main/java/ui/controller/MainController.java
@@ -3,11 +3,14 @@
import application.user.UserRegister;
import application.user.UserSignIn;
import domain.organization.Organization;
+import domain.organization.OrganizationDetails;
+import integration.InnsamlingskontrollenClient;
import integration.security.Sha256PasswordHasher;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.VBox;
import persistence.UserDao;
import ui.DonationSession;
import ui.Page;
@@ -24,6 +27,9 @@ public class MainController {
@FXML
private NavbarController navbarController;
+ @FXML
+ private VBox overlay;
+
private DonationSession donationSession;
private DonationFlowController donationFlowController;
@@ -79,6 +85,13 @@ public void loadPage(Page page) throws IOException {
if (controller instanceof DonationSessionAware c) {
c.setDonationSession(donationSession);
}
+ if (controller instanceof OrganizationController c) {
+ c.setOnDonate(org -> {
+ try { loadDonationPage(org); }
+ catch (IOException e) { throw new RuntimeException(e); }
+ });
+ c.setOnShowDetail(org -> showOrganizationDetail(org));
+ }
mainPane.setCenter(root);
navbarController.setActivePage(page);
@@ -89,4 +102,45 @@ public void loadPage(Page page) throws IOException {
public void loadDonationPage(Organization org) throws IOException {
donationFlowController.start(org);
}
+
+ public void showOrganizationDetail(Organization org) {
+ try {
+ FXMLLoader loader = new FXMLLoader(
+ getClass().getResource("/view/OrganizationDetail.fxml"));
+ Parent root = loader.load();
+
+ OrganizationDetails details =
+ InnsamlingskontrollenClient.fetchDetails(org.getUrl());
+
+ OrganizationDetailController controller = loader.getController();
+ controller.setOrganization(org, details);
+ controller.setOnClose(() -> {
+ overlay.setVisible(false);
+ overlay.setManaged(false);
+ overlay.getChildren().clear();
+ });
+ controller.setOnDonate(o -> {
+ overlay.setVisible(false);
+ overlay.setManaged(false);
+ overlay.getChildren().clear();
+ try {
+ loadDonationPage(o);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ overlay.getChildren().clear();
+ overlay.getChildren().add(root);
+ overlay.setVisible(true);
+ overlay.setManaged(true);
+ overlay.setStyle("-fx-background-color: rgba(0,0,0,0.5);");
+ overlay.setMaxWidth(Double.MAX_VALUE);
+ overlay.setMaxHeight(Double.MAX_VALUE);
+ overlay.setAlignment(javafx.geometry.Pos.CENTER);
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/src/main/java/ui/controller/OrganizationController.java b/src/main/java/ui/controller/OrganizationController.java
index e5f4ac2..ede0dd3 100644
--- a/src/main/java/ui/controller/OrganizationController.java
+++ b/src/main/java/ui/controller/OrganizationController.java
@@ -37,6 +37,8 @@ public class OrganizationController implements NavigationAware{
private Consumer onNavigate;
private Consumer onDonate;
+ private Consumer onShowDetail;
+
public void setOnNavigate(Consumer onNavigate) {
this.onNavigate = onNavigate;
@@ -46,10 +48,16 @@ public void setOnDonate(Consumer onDonate) {
this.onDonate = onDonate;
}
+ public void setOnShowDetail(Consumer onShowDetail) {
+ this.onShowDetail = onShowDetail;
+ }
+
public void initialize() {
try {
OrganizationDao dao = new OrganizationDao();
- allOrgs = dao.getAll();
+ allOrgs = dao.getAll().stream()
+ .filter(org -> "approved".equals(org.getStatus()))
+ .collect(Collectors.toList());
System.out.println("Fetched: " + allOrgs.size() + " organizations");
filtrertOrgs = new ArrayList<>(allOrgs);
showPage();
@@ -119,6 +127,7 @@ private VBox createCard(Organization org) {
donateBtn.setMaxWidth(Double.MAX_VALUE);
donateBtn.setOnAction(e -> onDonate.accept(org));
+ card.setOnMouseClicked(e -> onShowDetail.accept(org));
card.getChildren().addAll(name, orgnr, status, donateBtn);
return card;
}
diff --git a/src/main/java/ui/controller/OrganizationDetailController.java b/src/main/java/ui/controller/OrganizationDetailController.java
new file mode 100644
index 0000000..96a8b74
--- /dev/null
+++ b/src/main/java/ui/controller/OrganizationDetailController.java
@@ -0,0 +1,102 @@
+package ui.controller;
+
+import domain.organization.Organization;
+import domain.organization.OrganizationDetails;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.Hyperlink;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import ui.Page;
+
+import java.awt.Desktop;
+import java.net.URI;
+import java.util.function.Consumer;
+
+public class OrganizationDetailController {
+
+ @FXML private Label nameLabel;
+ @FXML private Label orgNumberLabel;
+ @FXML private Label verifiedLabel;
+ @FXML private Label descriptionLabel;
+ @FXML private ImageView logoImageView;
+ @FXML private Hyperlink readMoreLink;
+
+
+ private Organization organization;
+ private Consumer onDonate;
+ private Runnable onClose;
+
+ public void setOrganization(Organization org, OrganizationDetails details) {
+ this.organization = org;
+ nameLabel.setText(org.getName());
+ orgNumberLabel.setText("Org.nr: " + org.getOrgNumber());
+ verifiedLabel.setText(org.isPreApproved() ? "✓ Forhåndsgodkjent" : "✓ Verified");
+
+ if (details.getDescription() != null && !details.getDescription().isBlank()) {
+ String description = details.getDescription();
+ if (description.length() > 300) {
+ int lastPeriod = description.lastIndexOf(".", 300);
+ description = lastPeriod > 0
+ ? description.substring(0, lastPeriod + 1)
+ : description.substring(0, 300) + "...";
+ readMoreLink.setVisible(true);
+ readMoreLink.setManaged(true);
+ }
+ descriptionLabel.setText(description);
+ } else {
+ descriptionLabel.setText("No description available.");
+ }
+
+
+
+ if (details.getLogoUrl() != null && !details.getLogoUrl().isBlank()) {
+ try {
+ Image image = new Image(details.getLogoUrl(), true);
+ image.errorProperty().addListener((obs, old, error) -> {
+ if (error) {
+ logoImageView.setVisible(false);
+ }
+ });
+ logoImageView.setImage(image);
+ } catch (Exception e) {
+ logoImageView.setVisible(false);
+ }
+ } else {
+ logoImageView.setVisible(false);
+ }
+ }
+
+ public void setOnDonate(Consumer onDonate) {
+ this.onDonate = onDonate;
+ }
+
+ public void setOnClose(Runnable onClose) {
+ this.onClose = onClose;
+ }
+
+ @FXML
+ public void handleClose() {
+ if (onClose != null) onClose.run();
+ }
+
+ @FXML
+ public void handleDonate() {
+ if (onDonate != null) onDonate.accept(organization);
+ }
+
+ @FXML
+ public void handleVisitWebsite() {
+ try {
+ Desktop.getDesktop().browse(new URI(organization.getUrl()));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @FXML
+ public void handleReadMore() {
+ handleVisitWebsite();
+ }
+}
diff --git a/src/main/resources/css/Donation.css b/src/main/resources/css/Donation.css
index 68722d4..ec92d1f 100644
--- a/src/main/resources/css/Donation.css
+++ b/src/main/resources/css/Donation.css
@@ -96,27 +96,4 @@
.success-icon {
-fx-font-size: 48px;
-fx-text-fill: #2d6a4f;
-}
-
-.secondary-button {
- -fx-background-color: transparent;
- -fx-border-color: #1e3a8a;
- -fx-border-width: 1.5;
- -fx-border-radius: 6;
- -fx-background-radius: 6;
- -fx-text-fill: #1e3a8a;
- -fx-font-weight: bold;
- -fx-padding: 10 24 10 24;
- -fx-cursor: hand;
- -fx-font-size: 14px;
-}
-
-.secondary-button:hover {
- -fx-background-color: #f0f4ff;
-}
-
-.secondary-button:pressed {
- -fx-background-color: #dce6f7;
- -fx-scale-x: 0.97;
- -fx-scale-y: 0.97;
}
\ No newline at end of file
diff --git a/src/main/resources/css/Global.css b/src/main/resources/css/Global.css
index c7af883..7a2eeac 100644
--- a/src/main/resources/css/Global.css
+++ b/src/main/resources/css/Global.css
@@ -165,3 +165,25 @@
-fx-pref-height: 40px;
}
+.secondary-button {
+ -fx-background-color: transparent;
+ -fx-border-color: #1e3a8a;
+ -fx-border-width: 1.5;
+ -fx-border-radius: 6;
+ -fx-background-radius: 6;
+ -fx-text-fill: #1e3a8a;
+ -fx-font-weight: bold;
+ -fx-padding: 10 24 10 24;
+ -fx-cursor: hand;
+ -fx-font-size: 14px;
+}
+
+.secondary-button:hover {
+ -fx-background-color: #f0f4ff;
+}
+
+.secondary-button:pressed {
+ -fx-background-color: #dce6f7;
+ -fx-scale-x: 0.97;
+ -fx-scale-y: 0.97;
+}
diff --git a/src/main/resources/css/OrganizationView.css b/src/main/resources/css/OrganizationView.css
index 6904bfd..18c0a90 100644
--- a/src/main/resources/css/OrganizationView.css
+++ b/src/main/resources/css/OrganizationView.css
@@ -10,6 +10,12 @@
-fx-cursor: hand;
}
+.org-card:pressed {
+ -fx-scale-x: 0.98;
+ -fx-scale-y: 0.98;
+ -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.05), 4, 0, 0, 2);
+}
+
.org-name {
-fx-font-size: 20px;
}
@@ -55,4 +61,57 @@
.search-field:focused {
-fx-border-color: #3a86ff;
+}
+
+
+.detail-header {
+ -fx-background-color: #1e3a8a;
+ -fx-padding: 15 20 15 20;
+ -fx-alignment: CENTER_LEFT;
+ -fx-spacing: 10;
+}
+
+.detail-title {
+ -fx-font-size: 20px;
+ -fx-font-weight: bold;
+ -fx-text-fill: white;
+ -fx-hgrow: always;
+}
+
+.detail-close-button {
+ -fx-background-color: transparent;
+ -fx-text-fill: white;
+ -fx-font-size: 16px;
+ -fx-cursor: hand;
+ -fx-border-color: transparent;
+}
+
+.detail-close-button:hover {
+ -fx-background-color: rgba(255,255,255,0.2);
+ -fx-background-radius: 50;
+}
+
+.detail-content {
+ -fx-padding: 20;
+ -fx-spacing: 15;
+ -fx-background-color: white;
+}
+
+.detail-verified {
+ -fx-background-color: #e8f5e9;
+ -fx-text-fill: #2d6a4f;
+ -fx-padding: 4 10 4 10;
+ -fx-background-radius: 4;
+ -fx-font-size: 12px;
+}
+
+.detail-orgnr {
+ -fx-font-size: 12px;
+ -fx-text-fill: #666666;
+}
+
+.detail-description {
+ -fx-font-size: 13px;
+ -fx-text-fill: #333333;
+ -fx-wrap-text: true;
}
\ No newline at end of file
diff --git a/src/main/resources/view/MainView.fxml b/src/main/resources/view/MainView.fxml
index 6b655b0..671a97b 100644
--- a/src/main/resources/view/MainView.fxml
+++ b/src/main/resources/view/MainView.fxml
@@ -1,21 +1,22 @@
-
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
+
+
+
diff --git a/src/main/resources/view/OrganizationDetail.fxml b/src/main/resources/view/OrganizationDetail.fxml
new file mode 100644
index 0000000..7f46012
--- /dev/null
+++ b/src/main/resources/view/OrganizationDetail.fxml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+