Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v12.13.1
v24
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumping .nvmrc to Node v24 is likely incompatible with the current tooling stack (react-scripts@5.0.1 / react-app-rewired), and may break local development and CI installs/builds. Please either keep the Node version aligned with what the toolchain supports, or update the build tooling to versions that explicitly support Node 24 and verify in CI.

Suggested change
v24
v18

Copilot uses AI. Check for mistakes.
2 changes: 2 additions & 0 deletions default.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ HTTPS=true

REACT_APP_ENVIRONMENT=dev
REACT_APP_ACCESSIBILITY_STATEMENT_URL=https://uustatus.no/nb/erklaringer/publisert/698f231b-2177-4e76-bd93-628b24564dd8
REACT_APP_POSTHOG_KEY=
REACT_APP_POSTHOG_HOST=https://eu.i.posthog.com
REACT_APP_GEOID_CLIENT_ID=
REACT_APP_GEOID_AUTHORITY=
REACT_APP_GEOID_ISSUER=
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@fortawesome/pro-solid-svg-icons": "^6.1.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"@kartverket/geonorge-web-components": "^4.0.10",
"@posthog/react": "^1.8.2",
"@uiw/react-md-editor": "^3.20.5",
"classnames": "^2.2.6",
"connected-react-router": "^6.5.2",
Expand All @@ -21,6 +22,7 @@
"moment": "^2.24.0",
"moment-timezone": "^0.5.23",
"oidc-client-ts": "^3.3.0",
"posthog-js": "^1.360.0",
"r_map": "https://github.com/kartverket/r_map.git",
"react": "^18.1.0",
"react-app-polyfill": "^3.0.0",
Expand Down
4 changes: 3 additions & 1 deletion public/config.template.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,7 @@ window.__APP_CONFIG__ = {
REACT_APP_GEOID_JWKS_URI: "${REACT_APP_GEOID_JWKS_URI}",
REACT_APP_GEOID_KID: "${REACT_APP_GEOID_KID}",
REACT_APP_GEOID_N: "${REACT_APP_GEOID_N}",
REACT_APP_GEOID_BAATAUTHZ_APIURL: "${REACT_APP_GEOID_BAATAUTHZ_APIURL}"
REACT_APP_GEOID_BAATAUTHZ_APIURL: "${REACT_APP_GEOID_BAATAUTHZ_APIURL}",
REACT_APP_POSTHOG_KEY: "${REACT_APP_POSTHOG_KEY}",
REACT_APP_POSTHOG_HOST: "${REACT_APP_POSTHOG_HOST}"
};
8 changes: 8 additions & 0 deletions src/components/partials/Buttons/ApplicationButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from "react";
import PropTypes from "prop-types";
import { useDispatch } from "react-redux";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { usePostHog } from "@posthog/react";

// Actions
import { getResource } from "actions/ResourceActions";
Expand All @@ -15,6 +16,7 @@ import style from "components/partials/Buttons/Buttons.module.scss";

const ApplicationButton = (props) => {
const dispatch = useDispatch();
const posthog = usePostHog();

const handleButtonClick = () => {
const tagData = {
Expand All @@ -29,6 +31,12 @@ const ApplicationButton = (props) => {
metadata: tagData
})
);
posthog?.capture("application_link_clicked", {
title: props.metadata.Title,
uuid: props.metadata.Uuid,
distribution_url: props.metadata.DistributionUrl || props.metadata.DownloadUrl,
organization: props.metadata.Organization,
});
};

const isApplication = () => {
Expand Down
17 changes: 17 additions & 0 deletions src/components/partials/Buttons/DownloadButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { useDispatch, useSelector } from "react-redux";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { usePostHog } from "@posthog/react";

// Utils
import userManager from "utils/userManager";
Expand All @@ -25,6 +26,7 @@ import style from "components/partials/Buttons/Buttons.module.scss";

const DownloadButton = (props) => {
const dispatch = useDispatch();
const posthog = usePostHog();

// Redux store
const itemsToDownload = useSelector((state) => state.itemsToDownload);
Expand Down Expand Up @@ -159,11 +161,26 @@ const DownloadButton = (props) => {
metadata: tagData
})
);
posthog?.capture("download_link_clicked", {
title: props.metadata.Title,
uuid: props.metadata.Uuid,
distribution_url: props.metadata.DistributionUrl,
protocol: props.metadata.Protocol,
organization: props.metadata.Organization,
});
};

const addToDownloadListAction = () => {
const metadata = props.metadata;
setIsLoading(true);
posthog?.capture("download_added_to_basket", {
title: metadata.Title,
uuid: metadata.Uuid,
type_name: metadata.TypeName,
organization: metadata.Organization || metadata.ContactMetadata?.Organization,
access_is_open_data: metadata.AccessIsOpendata,
access_is_restricted: metadata.AccessIsRestricted,
});

if (metadata.TypeName === "series_historic" || metadata.TypeName === "series_collection") {
if (metadata.SerieDatasets) {
Expand Down
14 changes: 14 additions & 0 deletions src/components/partials/Buttons/MapButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { useDispatch, useSelector } from "react-redux";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { usePostHog } from "@posthog/react";

// Actions
import { removeMapItem, addMapItem } from "actions/MapItemActions";
Expand All @@ -15,6 +16,7 @@ import style from "components/partials/Buttons/Buttons.module.scss";

const MapButton = (props) => {
const dispatch = useDispatch();
const posthog = usePostHog();

// Redux store
const mapItems = useSelector((state) => state.mapItems);
Expand Down Expand Up @@ -119,12 +121,24 @@ const MapButton = (props) => {
const addToMap = (mapItem) => {
if (mapItem?.length) {
dispatch(addMapItem(mapItem));
posthog?.capture("map_item_added", {
title: props.metadata.Title,
uuid: props.metadata.Uuid,
protocol: props.metadata.DistributionProtocol || props.metadata.Protocol,
organization: props.metadata.Organization,
});
}
};

const removeFromMap = (mapItem) => {
if (mapItem?.length) {
dispatch(removeMapItem(mapItem));
posthog?.capture("map_item_removed", {
title: props.metadata.Title,
uuid: props.metadata.Uuid,
protocol: props.metadata.DistributionProtocol || props.metadata.Protocol,
organization: props.metadata.Organization,
});
}
};

Expand Down
17 changes: 16 additions & 1 deletion src/components/partials/FacetFilter/Facet.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { useDispatch } from "react-redux";
import classNames from "classnames/bind";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link } from "react-router-dom";
import { usePostHog } from "@posthog/react";

// Reducers
import { pushToDataLayer } from "reducers/TagManagerReducer";

// Helpers
import { getQueryStringFromFacets } from "helpers/FacetFilterHelpers";
import { getActiveFiltersFromSelectedFacets, getQueryStringFromFacets } from "helpers/FacetFilterHelpers";

// Components
import { ErrorBoundary } from "components/ErrorBoundary";
Expand All @@ -20,6 +21,7 @@ import style from "components/partials/FacetFilter/Facet.module.scss";

const Facet = (props) => {
const dispatch = useDispatch();
const posthog = usePostHog();

// State
const [checked, setChecked] = useState(false);
Expand Down Expand Up @@ -139,6 +141,19 @@ const Facet = (props) => {
facet: props.facet
})
);

const selectedFacets = props?.searchData?.selectedFacets || {};
const activeFilters = getActiveFiltersFromSelectedFacets(selectedFacets);

posthog?.capture("facet_filter_applied", {
facet_field: props.facetField,
facet_field_name: props.facetFieldNameTranslated,
facet_name: props.facet.Name,
facet_name_translated: props.facet.NameTranslated,
facet_count: props.facet.Count,
action: checked ? "remove" : "add",
active_filters: activeFilters,
});
Comment on lines +145 to +156
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The facet_filter_applied event is using active_filters derived from the current selectedFacets in props, but the click is about to add/remove the facet via navigation. This means the tracked active_filters will be stale (missing the newly added facet or still including the removed one). Consider computing the next selected facets (add/remove the clicked facet) before capturing, or capturing in a place that runs after searchData.selectedFacets has updated.

Copilot uses AI. Check for mistakes.
};

const renderFacet = () => {
Expand Down
12 changes: 11 additions & 1 deletion src/components/partials/SearchResults.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from "react";
import { useDispatch } from "react-redux";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link } from "react-router-dom";
import { usePostHog } from "@posthog/react";

// Geonorge WebComponents
// eslint-disable-next-line no-unused-vars
Expand All @@ -22,6 +23,7 @@ import style from "components/partials/SearchResults.module.scss";

export const SearchResults = ({ searchData, searchResultsType }) => {
const dispatch = useDispatch();
const posthog = usePostHog();

const getShowMoreLink = () => {
const newOffset = searchData?.offset + 25;
Expand Down Expand Up @@ -72,11 +74,19 @@ export const SearchResults = ({ searchData, searchResultsType }) => {
return localStorage.getItem("urlDownloadCsv");
};

const handleShowMoreClick = () => {
posthog?.capture("show_more_results_clicked", {
results_type: searchResultsType,
current_offset: searchData?.offset,
search_string: searchData?.searchString,
});
};

const renderShowMoreLink = () => {
return (
<div className={style.morecontainer}>
<gn-button color="default">
<Link to={{ search: getShowMoreLink() }} replace className={style.morebtn}>
<Link to={{ search: getShowMoreLink() }} replace className={style.morebtn} onClick={handleShowMoreClick}>
<span>{dispatch(getResource("ShowMoreResults", "Vis flere"))}</span>
<FontAwesomeIcon icon={"angle-down"} key="icon" />
</Link>
Expand Down
28 changes: 26 additions & 2 deletions src/components/partials/SearchResults/MetadataSearchResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useDispatch } from "react-redux";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link } from "react-router-dom";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { usePostHog } from "@posthog/react";

// Actions
import { getResource } from "actions/ResourceActions";
Expand All @@ -27,6 +28,7 @@ import style from "components/partials/SearchResults/MetadataSearchResult.module

const MetadataSearchResult = (props) => {
const dispatch = useDispatch();
const posthog = usePostHog();

// State
const [copied, setCopied] = useState(false);
Expand Down Expand Up @@ -170,21 +172,43 @@ const MetadataSearchResult = (props) => {
);
};

const handleResultClick = () => {
posthog?.capture("search_result_clicked", {
title: props.searchResult.Title,
uuid: props.searchResult.Uuid,
type: props.searchResult.Type,
organization: props.searchResult.Organization,
is_open_data: props.searchResult.IsOpenData || props.searchResult.AccessIsOpendata,
});
};

const renderLink = () => {
return props.metadata?.Uuid === props.searchResult.Uuid ? (
<span>{props.searchResult.Title}</span>
) : (
<Link to={`/metadata/${convertTextToUrlSlug(props.searchResult.Title)}/${props.searchResult.Uuid}`}>
<Link
to={`/metadata/${convertTextToUrlSlug(props.searchResult.Title)}/${props.searchResult.Uuid}`}
onClick={handleResultClick}
>
{props.searchResult.Title}
</Link>
);
};

const handleCopyUrl = () => {
setCopied(true);
posthog?.capture("service_url_copied", {
title: props.searchResult.Title,
uuid: props.searchResult.Uuid,
get_capabilities_url: props.searchResult.GetCapabilitiesUrl,
});
};

const renderCopyUrl = () => {
return (props.searchResult.Type === "service" || props.searchResult.Type === "Tjeneste") &&
props.searchResult.GetCapabilitiesUrl !== undefined ? (
<ErrorBoundary>
<CopyToClipboard onCopy={() => setCopied(true)} text={props.searchResult.GetCapabilitiesUrl}>
<CopyToClipboard onCopy={handleCopyUrl} text={props.searchResult.GetCapabilitiesUrl}>
<span title={props.searchResult.GetCapabilitiesUrl} className={style.url}>
Kopier lenke <FontAwesomeIcon icon={["far", "copy"]} />{" "}
{copied ? <span>Lenke kopiert til utklippstavle</span> : null}
Expand Down
27 changes: 26 additions & 1 deletion src/components/routes/Home.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Dependencies
import React, { useEffect } from "react";
import React, { useEffect, useRef } from "react";
import { Helmet } from "react-helmet-async";
import { useDispatch, useSelector } from "react-redux";
import { Link, useRouteLoaderData } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { usePostHog } from "@posthog/react";

// Geonorge WebComponents
// eslint-disable-next-line no-unused-vars
Expand All @@ -12,6 +13,9 @@ import { BreadcrumbList, HeadingText, GnShortcutButton } from "@kartverket/geono
// Actions
import { getResource } from "actions/ResourceActions";

// Helpers
import { getActiveFiltersFromSelectedFacets } from "helpers/FacetFilterHelpers";

// Components
import SelectedFacets from "components/partials/SelectedFacets";
import SearchResults from "components/partials/SearchResults";
Expand All @@ -23,12 +27,33 @@ import style from "./Home.module.scss";
const Home = () => {
const dispatch = useDispatch();
const { searchData, params } = useRouteLoaderData("root");
const posthog = usePostHog();
const lastCapturedSearchString = useRef(null);

// Redux store
const auth = useSelector((state) => state.auth);
const environment = useSelector((state) => state.environment);
const selectedLanguage = useSelector((state) => state.selectedLanguage);

useEffect(() => {
const searchString = searchData?.searchString || "";
const selectedFacets = searchData?.selectedFacets || {};
const activeFilters = getActiveFiltersFromSelectedFacets(selectedFacets);

if (posthog && searchString !== lastCapturedSearchString.current) {
lastCapturedSearchString.current = searchString;
if (searchString.length > 0) {
posthog.capture("search_performed", {
search_string: searchString,
results_type: params?.searchResultsType || "metadata",
num_found_metadata: searchData?.results?.metadata?.NumFound || 0,
num_found_articles: searchData?.results?.articles?.NumFound || 0,
active_filters: activeFilters,
});
}
}
}, [searchData, params, posthog]);

useEffect(() => {
const isLoggedIn = !!auth?.user?.access_token?.length;
// Todo fix problem navigation from MainNavigationContainer https://medium.com/@fabrizio.azzarri/fixing-the-next-js-15-react-19-removechild-dom-error-a33b57cbc3b1
Expand Down
11 changes: 11 additions & 0 deletions src/components/routes/Metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Helmet } from "react-helmet-async";
import Moment from "react-moment";
import { useDispatch, useSelector } from "react-redux";
import { useLoaderData, useLocation, useParams } from "react-router-dom";
import { usePostHog } from "@posthog/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import DatePicker from "react-datepicker";
import MDEditor from "@uiw/react-md-editor";
Expand Down Expand Up @@ -54,6 +55,7 @@ import { scrollToTop } from "helpers/GuiHelpers";
const Metadata = () => {

const dispatch = useDispatch();
const posthog = usePostHog();
const params = useParams();
const location = useLocation();

Expand Down Expand Up @@ -234,6 +236,15 @@ const Metadata = () => {
metadata: tagData
})
);
posthog?.capture("metadata_detail_viewed", {
title: getTitle(),
uuid: metadata.Uuid,
type: metadata.Type,
access_is_open_data: metadata.AccessIsOpendata,
access_is_restricted: metadata.AccessIsRestricted,
organization: metadata.ContactMetadata?.Organization || null,
theme: metadata.Theme || null,
});
};

const renderDatasetLanguage = () => {
Expand Down
Loading
Loading