diff --git a/env/env-settings.ts b/env/env-settings.ts index 0e142a2c3..6529790df 100644 --- a/env/env-settings.ts +++ b/env/env-settings.ts @@ -1,4 +1,7 @@ export abstract class EnvSettings { + abstract PRODUCTION_BASE_URL: string; + abstract SANDBOX_BASE_URL: string; + abstract ENTRYSCAPE_DATASETS_PATH: string; abstract ENTRYSCAPE_SPECS_PATH: string; abstract ENTRYSCAPE_TERMS_PATH: string; diff --git a/env/settings.dev.ts b/env/settings.dev.ts index 5bfbb57af..4a4112aa0 100644 --- a/env/settings.dev.ts +++ b/env/settings.dev.ts @@ -2,9 +2,10 @@ import { generateRandomKey } from "../utilities"; import { EnvSettings } from "./env-settings"; export class Settings_Dev extends EnvSettings { - ENTRYSCAPE_DATASETS_PATH = "admin.dataportal.se"; - // ENTRYSCAPE_DATASETS_PATH="sandbox.admin.dataportal.se" + PRODUCTION_BASE_URL = "https://www.dataportal.se"; + SANDBOX_BASE_URL = "https://www-sandbox.dataportal.se"; + ENTRYSCAPE_DATASETS_PATH = "admin.dataportal.se"; ENTRYSCAPE_SPECS_PATH = "admin.dataportal.se"; ENTRYSCAPE_TERMS_PATH = "editera.dataportal.se"; ENTRYSCAPE_MQA_PATH = "admin.dataportal.se"; diff --git a/env/settings.prod.ts b/env/settings.prod.ts index e0145398f..fa4eed991 100644 --- a/env/settings.prod.ts +++ b/env/settings.prod.ts @@ -2,6 +2,9 @@ import { generateRandomKey } from "../utilities"; import { EnvSettings } from "./env-settings"; export class Settings_Prod extends EnvSettings { + PRODUCTION_BASE_URL = "https://www.dataportal.se"; + SANDBOX_BASE_URL = "https://www-sandbox.dataportal.se"; + ENTRYSCAPE_DATASETS_PATH = "admin.dataportal.se"; ENTRYSCAPE_SPECS_PATH = "admin.dataportal.se"; ENTRYSCAPE_TERMS_PATH = "editera.dataportal.se"; diff --git a/env/settings.sandbox.ts b/env/settings.sandbox.ts index 07df3d8ba..e752dc5dd 100644 --- a/env/settings.sandbox.ts +++ b/env/settings.sandbox.ts @@ -2,6 +2,9 @@ import { generateRandomKey } from "../utilities"; import { EnvSettings } from "./env-settings"; export class Settings_Sandbox extends EnvSettings { + PRODUCTION_BASE_URL = "https://www.dataportal.se"; + SANDBOX_BASE_URL = "https://www-sandbox.dataportal.se"; + ENTRYSCAPE_DATASETS_PATH = "sandbox.admin.dataportal.se"; ENTRYSCAPE_SPECS_PATH = "sandbox.admin.dataportal.se"; ENTRYSCAPE_TERMS_PATH = "sandbox.editera.dataportal.se"; diff --git a/env/settings.test.ts b/env/settings.test.ts index 97cc68cac..08433ac8c 100644 --- a/env/settings.test.ts +++ b/env/settings.test.ts @@ -2,6 +2,9 @@ import { generateRandomKey } from "../utilities"; import { EnvSettings } from "./env-settings"; export class Settings_Test extends EnvSettings { + PRODUCTION_BASE_URL = "https://www.dataportal.se"; + SANDBOX_BASE_URL = "https://www-sandbox.dataportal.se"; + ENTRYSCAPE_DATASETS_PATH = "admin.dataportal.se"; ENTRYSCAPE_SPECS_PATH = "editera.dataportal.se"; ENTRYSCAPE_TERMS_PATH = "editera.dataportal.se"; diff --git a/features/entryscape/mqa-category-page/index.tsx b/features/entryscape/mqa-category-page/index.tsx index cdf79fc2d..985b0e16b 100644 --- a/features/entryscape/mqa-category-page/index.tsx +++ b/features/entryscape/mqa-category-page/index.tsx @@ -1,4 +1,5 @@ import { usePathname } from "next/navigation"; +import useTranslation from "next-translate/useTranslation"; import { FC, useContext, useEffect } from "react"; import { Container } from "@/components/layout/container"; @@ -9,13 +10,14 @@ import { linkBase } from "@/utilities"; export const MQACategoryPage: FC = () => { const entry = useContext(EntrystoreContext); + const { lang, t } = useTranslation(); const { setBreadcrumb } = useContext(SettingsContext); const pathname = usePathname(); useEntryScapeBlocks({ entrystoreBase: entry.env.ENTRYSCAPE_MQA_PATH, env: entry.env, - lang: "sv", + lang: lang, pageType: "mqa", context: entry.context, esId: entry.esId, @@ -27,10 +29,10 @@ export const MQACategoryPage: FC = () => { crumbs: [ { name: "start", link: { ...linkBase, link: "/" } }, { - name: "Metadatakvalitet per katalog", + name: t("routes|metadata$title"), link: { ...linkBase, - link: `/metadatakvalitet`, + link: `/${t(`routes|metadata$path`)}`, }, }, ], diff --git a/features/entryscape/mqa-page/index.tsx b/features/entryscape/mqa-page/index.tsx index 68f3b2d02..8c9ff2d0e 100644 --- a/features/entryscape/mqa-page/index.tsx +++ b/features/entryscape/mqa-page/index.tsx @@ -10,9 +10,9 @@ import { linkBase } from "@/utilities"; export const MQAPage: FC = () => { const { env, setBreadcrumb } = useContext(SettingsContext); - const { lang } = useTranslation(); + const { lang, t } = useTranslation(); const pathname = usePathname(); - const pageTitle = "Metadatakvalitet per katalog"; + const pageTitle = t("routes|metadata$title"); useEntryScapeBlocks({ entrystoreBase: `https://${env.ENTRYSCAPE_MQA_PATH}/store`, diff --git a/features/search/search-page/search-page-entryscape/index.tsx b/features/search/search-page/search-page-entryscape/index.tsx index 33325b980..d273bcbe2 100644 --- a/features/search/search-page/search-page-entryscape/index.tsx +++ b/features/search/search-page/search-page-entryscape/index.tsx @@ -11,7 +11,7 @@ import { SearchPageSelector } from "@/features/search/search-page-selector"; import { SearchResults } from "@/features/search/search-results"; import SearchProvider, { SearchContext } from "@/providers/search-provider"; import { SettingsContext } from "@/providers/settings-provider"; -import { linkBase } from "@/utilities"; +import { handleLocale, linkBase } from "@/utilities"; import { createSearchProviderSettings } from "./search-page-provider-settings"; @@ -29,6 +29,11 @@ export const SearchPageEntryscape: FC = ({ const [query, setQuery] = useState(""); const router = useRouter(); + // Remove locale from path if it's the default locale + useEffect(() => { + handleLocale(window.location.pathname, lang, router.asPath, router); + }, [router.asPath]); + useEffect(() => { if (typeof window === "undefined") return; diff --git a/features/search/search-page/search-page-entryscape/search-page-provider-settings.ts b/features/search/search-page/search-page-entryscape/search-page-provider-settings.ts index 6a0c5ca6e..40a9e94a6 100644 --- a/features/search/search-page/search-page-entryscape/search-page-provider-settings.ts +++ b/features/search/search-page/search-page-entryscape/search-page-provider-settings.ts @@ -1,6 +1,10 @@ import { EnvSettings } from "@/env"; import { SearchSortOrder } from "@/providers/search-provider"; import { ESRdfType, ESType } from "@/types/entrystore-core"; +import { + specsPathResolver, + conceptsPathResolver, +} from "@/utilities/entrystore/entrystore-helpers"; interface FacetConfig { resource: string; @@ -285,6 +289,7 @@ export function createSearchProviderSettings(env: EnvSettings, lang: string) { path: `/specifications/`, titleResource: "dcterms:title", descriptionResource: "dcterms:description", + pathResolver: specsPathResolver, }, }, facetSpecification: { @@ -372,6 +377,7 @@ export function createSearchProviderSettings(env: EnvSettings, lang: string) { path: `/concepts/`, titleResource: "http://www.w3.org/2004/02/skos/core#prefLabel", descriptionResource: "http://www.w3.org/2004/02/skos/core#definition", + pathResolver: conceptsPathResolver, }, }, facetSpecification: { diff --git a/locales/en/routes.json b/locales/en/routes.json index ec27dc9c0..b7467493a 100644 --- a/locales/en/routes.json +++ b/locales/en/routes.json @@ -64,6 +64,10 @@ "path": "sv", "title": "Svenska" }, + "metadata": { + "path": "metadatakvalitet", + "title": "Metadata quality per catalog" + }, "news": { "path": "", "title": "News" diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 000000000..44afedeff --- /dev/null +++ b/middleware.ts @@ -0,0 +1,54 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; + +import i18n from "./i18n"; + +function getLocale(request: NextRequest): string { + const acceptLanguage = request.headers.get("accept-language"); + if (acceptLanguage) { + const [browserLocale] = acceptLanguage.split(","); + if (i18n.locales.includes(browserLocale as string)) { + return browserLocale; + } + } + return i18n.defaultLocale; +} + +export function middleware(request: NextRequest) { + const pathname = request.nextUrl.pathname; + + // Check if the pathname already has a locale + const pathnameHasLocale = i18n.locales.some( + (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`, + ); + + if (pathnameHasLocale) { + // If it's the default locale, redirect to remove it from URL + if (pathname.startsWith(`/${i18n.defaultLocale}/`)) { + const newPathname = pathname.replace(`/${i18n.defaultLocale}`, "") || "/"; + const newUrl = new URL(newPathname, request.url); + newUrl.search = request.nextUrl.search; + return NextResponse.redirect(newUrl); + } + return NextResponse.next(); + } + + const locale = getLocale(request); + + // Only add locale to URL if it's not the default locale + if (locale !== i18n.defaultLocale) { + const newUrl = new URL(`/${locale}${pathname}`, request.url); + newUrl.search = request.nextUrl.search; + return NextResponse.redirect(newUrl); + } + + // For default locale, just continue without modification + return NextResponse.next(); +} + +export const config = { + matcher: [ + "/((?!api|_next/static|_next/image|favicon.ico|__ENV.js|manifest.json|.*\\.(?:jpg|jpeg|gif|png|svg|woff|woff2)).*)", + "/", + ], +}; diff --git a/middlewere.ts b/middlewere.ts deleted file mode 100644 index 7139ed3e4..000000000 --- a/middlewere.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -const PUBLIC_FILE = /\.(.*)$/; - -export async function middleware(req: NextRequest) { - if ( - req.nextUrl.pathname.startsWith("/_next") || - req.nextUrl.pathname.includes("/api/") || - PUBLIC_FILE.test(req.nextUrl.pathname) - ) { - return; - } - - if (req.nextUrl.locale === "default") { - const locale = req.cookies.get("NEXT_LOCALE")?.value || "sv"; - - return NextResponse.redirect( - new URL( - `/${locale}${req.nextUrl.pathname}${req.nextUrl.search}`, - req.url, - ), - ); - } -} diff --git a/pages/concepts/[concept]/[param].tsx b/pages/concepts/[concept]/[param].tsx index 2340ccecd..c2e1016cc 100644 --- a/pages/concepts/[concept]/[param].tsx +++ b/pages/concepts/[concept]/[param].tsx @@ -1,19 +1,53 @@ -import { GetServerSidePropsContext } from "next/types"; +import { useRouter } from "next/router"; +import { useContext, useEffect, useState } from "react"; +import { ConceptPage } from "@/features/entryscape/concept-page"; +import { EntrystoreProvider } from "@/providers/entrystore-provider"; +import { SettingsContext } from "@/providers/settings-provider"; import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redirect"; export default function Concept() { - return null; -} + const { env } = useContext(SettingsContext); + const router = useRouter(); + const { concept, param } = router.query || {}; + const [resourceUri, setResourceUri] = useState(null); + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!concept || !param) return; + const isSandbox = window.location.host.includes("sandbox"); + + const data = await handleEntryStoreRedirect( + { + pathPrefix: "/concepts", + redirectPath: "/concepts", + entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", + param: concept, + secondParam: param as string, + }, + router, + router.locale || "sv", + isSandbox, + ); + + if (data?.resourceUri) { + setResourceUri(data.resourceUri); + } + }; -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect(context, { - pathPrefix: "/concepts", - redirectPath: "/concepts", - entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", - paramName: "concept", - secondParamName: "param", - }); -}; + fetchEntryStoreProps(); + }, [concept, param]); + + if (!resourceUri) return null; + + return ( + + + + ); +} diff --git a/pages/concepts/[concept]/index.tsx b/pages/concepts/[concept]/index.tsx index 17a57b470..ce0370295 100644 --- a/pages/concepts/[concept]/index.tsx +++ b/pages/concepts/[concept]/index.tsx @@ -1,6 +1,5 @@ import { useRouter } from "next/router"; -import { GetServerSidePropsContext } from "next/types"; -import { useContext } from "react"; +import { useContext, useEffect, useState } from "react"; import { ConceptPage } from "@/features/entryscape/concept-page"; import { EntrystoreProvider } from "@/providers/entrystore-provider"; @@ -9,8 +8,54 @@ import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redi export default function Concept() { const { env } = useContext(SettingsContext); - const { query } = useRouter() || {}; - const { concept } = query || {}; + const router = useRouter(); + const { concept } = router.query || {}; + const [resourceUri, setResourceUri] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!concept) return; + const isSandbox = window.location.host.includes("sandbox"); + + const data = await handleEntryStoreRedirect( + { + pathPrefix: "/concepts", + redirectPath: "/concepts", + entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", + param: concept, + }, + router, + router.locale || "sv", + isSandbox, + ); + + if (data?.resourceUri) { + setResourceUri(data.resourceUri); + } + setIsLoading(false); + }; + + fetchEntryStoreProps(); + }, [concept]); + + if (isLoading) { + return null; + } + + if (resourceUri) { + return ( + + + + ); + } + const ids = (typeof concept === "string" && concept.split("_")) || []; const eid = ids.pop() || ""; const cid = ids.join("_"); @@ -27,14 +72,3 @@ export default function Concept() { ); } - -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect(context, { - pathPrefix: "/concepts", - redirectPath: "/concepts", - entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", - paramName: "concept", - }); -}; diff --git a/pages/externalconcept/[...concept].tsx b/pages/externalconcept/[...concept].tsx index 4ff4ee911..b002e2d45 100644 --- a/pages/externalconcept/[...concept].tsx +++ b/pages/externalconcept/[...concept].tsx @@ -1,18 +1,32 @@ -import { GetServerSidePropsContext } from "next/types"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redirect"; export default function Concept() { + const router = useRouter(); + const { concept } = router.query; + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!concept) return; + const isSandbox = window.location.host.includes("sandbox"); + + await handleEntryStoreRedirect( + { + pathPrefix: "/concepts", + redirectPath: "/concepts", + entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", + param: concept, + }, + router, + router.locale || "sv", + isSandbox, + ); + }; + + fetchEntryStoreProps(); + }, [concept]); + return null; } - -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect(context, { - pathPrefix: "/concepts", - redirectPath: "/concepts", - entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", - paramName: "concept", - }); -}; diff --git a/pages/externalconcept/index.tsx b/pages/externalconcept/index.tsx index 4b673b998..65ae49936 100644 --- a/pages/externalconcept/index.tsx +++ b/pages/externalconcept/index.tsx @@ -1,21 +1,32 @@ -import { GetServerSidePropsContext } from "next/types"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redirect"; export default function Concept() { + const router = useRouter(); + const { resource } = router.query; + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!resource) return; + const isSandbox = window.location.host.includes("sandbox"); + + await handleEntryStoreRedirect( + { + pathPrefix: "/concepts", + redirectPath: "/concepts", + entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", + }, + router, + router.locale || "sv", + isSandbox, + resource as string, + ); + }; + + fetchEntryStoreProps(); + }, [resource]); + return null; } - -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect( - context, - { - pathPrefix: "/concepts", - redirectPath: "/concepts", - entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", - }, - context.query.resource as string, - ); -}; diff --git a/pages/externalspecification/[...specification].tsx b/pages/externalspecification/[...specification].tsx index df28dda29..024e663b8 100644 --- a/pages/externalspecification/[...specification].tsx +++ b/pages/externalspecification/[...specification].tsx @@ -1,18 +1,31 @@ -import { GetServerSidePropsContext } from "next/types"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redirect"; export default function Specification() { + const router = useRouter(); + const { specification } = router.query; + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!specification) return; + const isSandbox = window.location.host.includes("sandbox"); + + await handleEntryStoreRedirect( + { + pathPrefix: "/concepts", + redirectPath: "/concepts", + entrystorePathKey: "ENTRYSCAPE_SPECS_PATH", + param: specification, + }, + router, + router.locale || "sv", + isSandbox, + ); + }; + + fetchEntryStoreProps(); + }, [specification]); return null; } - -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect(context, { - pathPrefix: "/specifications", - redirectPath: "/specifications", - entrystorePathKey: "ENTRYSCAPE_SPECS_PATH", - paramName: "specification", - }); -}; diff --git a/pages/externalspecification/index.tsx b/pages/externalspecification/index.tsx index 8e6def31b..875271594 100644 --- a/pages/externalspecification/index.tsx +++ b/pages/externalspecification/index.tsx @@ -1,21 +1,32 @@ -import { GetServerSidePropsContext } from "next/types"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redirect"; export default function Specification() { + const router = useRouter(); + const { resource } = router.query; + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!resource) return; + const isSandbox = window.location.host.includes("sandbox"); + + await handleEntryStoreRedirect( + { + pathPrefix: "/specifications", + redirectPath: "/specifications", + entrystorePathKey: "ENTRYSCAPE_SPECS_PATH", + }, + router, + router.locale || "sv", + isSandbox, + resource as string, + ); + }; + + fetchEntryStoreProps(); + }, [resource]); + return null; } - -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect( - context, - { - pathPrefix: "/specifications", - redirectPath: "/specifications", - entrystorePathKey: "ENTRYSCAPE_SPECS_PATH", - }, - context.query.resource as string, - ); -}; diff --git a/pages/externalterminology/[...terminology].tsx b/pages/externalterminology/[...terminology].tsx index 77d4156c8..9155bed7f 100644 --- a/pages/externalterminology/[...terminology].tsx +++ b/pages/externalterminology/[...terminology].tsx @@ -1,18 +1,32 @@ -import { GetServerSidePropsContext } from "next/types"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redirect"; export default function Terminology() { + const router = useRouter(); + const { terminology } = router.query; + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!terminology) return; + const isSandbox = window.location.host.includes("sandbox"); + + await handleEntryStoreRedirect( + { + pathPrefix: "/concepts", + redirectPath: "/terminology", + entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", + param: terminology, + }, + router, + router.locale || "sv", + isSandbox, + ); + }; + + fetchEntryStoreProps(); + }, [terminology]); + return null; } - -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect(context, { - pathPrefix: "/concepts", - redirectPath: "/terminology", - entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", - paramName: "terminology", - }); -}; diff --git a/pages/externalterminology/index.tsx b/pages/externalterminology/index.tsx index 0501a982c..3c6bb9da1 100644 --- a/pages/externalterminology/index.tsx +++ b/pages/externalterminology/index.tsx @@ -1,21 +1,32 @@ -import { GetServerSidePropsContext } from "next/types"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redirect"; export default function Terminology() { + const router = useRouter(); + const { resource } = router.query; + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!resource) return; + const isSandbox = window.location.host.includes("sandbox"); + + await handleEntryStoreRedirect( + { + pathPrefix: "/concepts", + redirectPath: "/terminology", + entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", + }, + router, + router.locale || "sv", + isSandbox, + resource as string, + ); + }; + + fetchEntryStoreProps(); + }, [resource]); + return null; } - -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect( - context, - { - pathPrefix: "/concepts", - redirectPath: "/terminology", - entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", - }, - context.query.resource as string, - ); -}; diff --git a/pages/specifications/[spec]/[param].tsx b/pages/specifications/[spec]/[param].tsx index 0330b0e78..a7bd9f70d 100644 --- a/pages/specifications/[spec]/[param].tsx +++ b/pages/specifications/[spec]/[param].tsx @@ -1,19 +1,53 @@ -import { GetServerSidePropsContext } from "next/types"; +import { useRouter } from "next/router"; +import { useContext, useEffect, useState } from "react"; +import { SpecificationPage } from "@/features/entryscape/specification-page"; +import { EntrystoreProvider } from "@/providers/entrystore-provider"; +import { SettingsContext } from "@/providers/settings-provider"; import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redirect"; export default function Specification() { - return null; -} + const { env } = useContext(SettingsContext); + const router = useRouter(); + const { spec, param } = router.query || {}; + const [resourceUri, setResourceUri] = useState(null); + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!spec || !param) return; + const isSandbox = window.location.host.includes("sandbox"); + + const data = await handleEntryStoreRedirect( + { + pathPrefix: "/specifications", + redirectPath: "/specifications", + entrystorePathKey: "ENTRYSCAPE_SPECS_PATH", + param: spec, + secondParam: param as string, + }, + router, + router.locale || "sv", + isSandbox, + ); + + if (data?.resourceUri) { + setResourceUri(data.resourceUri); + } + }; -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect(context, { - pathPrefix: "/specifications", - redirectPath: "/specifications", - entrystorePathKey: "ENTRYSCAPE_SPECS_PATH", - paramName: "spec", - secondParamName: "param", - }); -}; + fetchEntryStoreProps(); + }, [spec, param]); + + if (!resourceUri) return null; + + return ( + + + + ); +} diff --git a/pages/specifications/[spec]/index.tsx b/pages/specifications/[spec]/index.tsx index dba243deb..2a0340c9d 100644 --- a/pages/specifications/[spec]/index.tsx +++ b/pages/specifications/[spec]/index.tsx @@ -1,6 +1,5 @@ import { useRouter } from "next/router"; -import { GetServerSidePropsContext } from "next/types"; -import { useContext } from "react"; +import { useContext, useEffect, useState } from "react"; import { SpecificationPage } from "@/features/entryscape/specification-page"; import { EntrystoreProvider } from "@/providers/entrystore-provider"; @@ -9,8 +8,54 @@ import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redi export default function Specification() { const { env } = useContext(SettingsContext); - const { query } = useRouter() || {}; - const { spec } = query || {}; + const router = useRouter(); + const { spec } = router.query || {}; + const [resourceUri, setResourceUri] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!spec) return; + const isSandbox = window.location.host.includes("sandbox"); + + const data = await handleEntryStoreRedirect( + { + pathPrefix: "/specifications", + redirectPath: "/specifications", + entrystorePathKey: "ENTRYSCAPE_SPECS_PATH", + param: spec, + }, + router, + router.locale || "sv", + isSandbox, + ); + + if (data?.resourceUri) { + setResourceUri(data.resourceUri); + } + setIsLoading(false); + }; + + fetchEntryStoreProps(); + }, [spec]); + + if (isLoading) { + return null; + } + + if (resourceUri) { + return ( + + + + ); + } + const ids = (typeof spec === "string" && spec.split("_")) || []; const eid = ids.pop() || ""; const cid = ids.join("_"); @@ -29,14 +74,3 @@ export default function Specification() { ); } } - -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect(context, { - pathPrefix: "/specifications", - redirectPath: "/specifications", - entrystorePathKey: "ENTRYSCAPE_SPECS_PATH", - paramName: "spec", - }); -}; diff --git a/pages/terminology/[term]/[param].tsx b/pages/terminology/[term]/[param].tsx index c149ad5ba..78ef21c44 100644 --- a/pages/terminology/[term]/[param].tsx +++ b/pages/terminology/[term]/[param].tsx @@ -1,19 +1,53 @@ -import { GetServerSidePropsContext } from "next/types"; +import { useRouter } from "next/router"; +import { useContext, useEffect, useState } from "react"; +import { ConceptPage } from "@/features/entryscape/concept-page"; +import { EntrystoreProvider } from "@/providers/entrystore-provider"; +import { SettingsContext } from "@/providers/settings-provider"; import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redirect"; export default function Terminology() { - return null; -} + const { env } = useContext(SettingsContext); + const router = useRouter(); + const { term, param } = router.query || {}; + const [resourceUri, setResourceUri] = useState(null); + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!term || !param) return; + const isSandbox = window.location.host.includes("sandbox"); + + const data = await handleEntryStoreRedirect( + { + pathPrefix: "/concepts", + redirectPath: "/terminology", + entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", + param: term, + secondParam: param as string, + }, + router, + router.locale || "sv", + isSandbox, + ); + + if (data?.resourceUri) { + setResourceUri(data.resourceUri); + } + }; -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect(context, { - pathPrefix: "/concepts", - redirectPath: "/terminology", - entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", - paramName: "term", - secondParamName: "param", - }); -}; + fetchEntryStoreProps(); + }, [term, param]); + + if (!resourceUri) return null; + + return ( + + + + ); +} diff --git a/pages/terminology/[term]/index.tsx b/pages/terminology/[term]/index.tsx index 8b846c954..84e9a44a9 100644 --- a/pages/terminology/[term]/index.tsx +++ b/pages/terminology/[term]/index.tsx @@ -1,6 +1,5 @@ -import { GetServerSidePropsContext } from "next"; import { useRouter } from "next/router"; -import { useContext } from "react"; +import { useContext, useEffect, useState } from "react"; import { ConceptPage } from "@/features/entryscape/concept-page"; import { EntrystoreProvider } from "@/providers/entrystore-provider"; @@ -9,8 +8,54 @@ import { handleEntryStoreRedirect } from "@/utilities/entrystore/entrystore-redi export default function Terminology() { const { env } = useContext(SettingsContext); - const { query } = useRouter() || {}; - const { term } = query || {}; + const router = useRouter(); + const { term } = router.query || {}; + const [resourceUri, setResourceUri] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchEntryStoreProps = async () => { + if (!term) return; + const isSandbox = window.location.host.includes("sandbox"); + + const data = await handleEntryStoreRedirect( + { + pathPrefix: "/concepts", + redirectPath: "/terminology", + entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", + param: term, + }, + router, + router.locale || "sv", + isSandbox, + ); + + if (data?.resourceUri) { + setResourceUri(data.resourceUri); + } + setIsLoading(false); + }; + + fetchEntryStoreProps(); + }, [term]); + + if (isLoading) { + return null; + } + + if (resourceUri) { + return ( + + + + ); + } + const ids = (typeof term === "string" && term.split("_")) || []; const eid = ids.pop() || ""; const cid = ids.join("_"); @@ -27,14 +72,3 @@ export default function Terminology() { ); } - -export const getServerSideProps = async ( - context: GetServerSidePropsContext, -) => { - return handleEntryStoreRedirect(context, { - pathPrefix: "/concepts", - redirectPath: "/terminology", - entrystorePathKey: "ENTRYSCAPE_TERMS_PATH", - paramName: "term", - }); -}; diff --git a/providers/entrystore-provider/index.tsx b/providers/entrystore-provider/index.tsx index 83567a182..00cc55ca2 100644 --- a/providers/entrystore-provider/index.tsx +++ b/providers/entrystore-provider/index.tsx @@ -9,7 +9,7 @@ import { SettingsUtil } from "@/env/settings-util"; import { ESEntry, PageType } from "@/types/entrystore-core"; import { OrganisationData } from "@/types/organisation"; import { ESFacetField, ESFacetFieldValue } from "@/types/search"; -import { Choice, fetchDCATMeta } from "@/utilities"; +import { Choice, fetchDCATMeta, handleLocale } from "@/utilities"; import { formatTerminologyAddress, getContactEmail, @@ -40,8 +40,9 @@ const defaultESEntry: ESEntry = { export interface EntrystoreProviderProps { env: EnvSettings; children: ReactNode; - cid: string; - eid: string; + cid?: string; + eid?: string; + rUri?: string; entryUri?: string; entrystoreUrl?: string; includeContact?: boolean; @@ -61,6 +62,7 @@ export const EntrystoreProvider: FC = ({ children, cid, eid, + rUri, entrystoreUrl, includeContact, pageType, @@ -69,6 +71,8 @@ export const EntrystoreProvider: FC = ({ const [state, setState] = useState(defaultESEntry); const router = useRouter(); const { lang, t } = useTranslation(); + let entry: Entry; + let resourceUri: string; const entrystoreService = EntrystoreService.getInstance({ baseUrl: @@ -97,25 +101,39 @@ export const EntrystoreProvider: FC = ({ }; }, [pageType]); + // Remove locale from path if it's the default locale + useEffect(() => { + handleLocale(window.location.pathname, lang, router.asPath, router); + }, [router.asPath]); + useEffect(() => { fetchEntry(); }, []); const fetchEntry = async () => { try { - const entry: Entry = await entrystoreService.getEntry(cid, eid); + if (cid && eid) { + entry = await entrystoreService.getEntry(cid, eid); + resourceUri = entry.getResourceURI(); + } else if (rUri) { + resourceUri = rUri; + entry = await entrystoreService.getEntryByResourceURI(rUri); + } if (!entry) return router.push("/404"); const metadata = entry.getAllMetadata(); - const resourceUri = entry.getResourceURI(); // Parallel fetch for publisher info // TODO: Remove this when concepts and terminologies are moved to admin.dataportal.se const publisherEntrystoreService = pageType === "concept" || pageType === "terminology" ? EntrystoreService.getInstance({ - baseUrl: `https://admin.dataportal.se/store`, + baseUrl: `https://${ + entry.getEntryInfo().getMetadataURI().includes("sandbox") + ? "sandbox." + : "" + }admin.dataportal.se/store`, lang, t, }) @@ -161,6 +179,7 @@ export const EntrystoreProvider: FC = ({ resourceUri, entrystoreService, publisherEntry, + defaultESEntry.env, ); setState({ @@ -181,6 +200,7 @@ export const EntrystoreProvider: FC = ({ resourceUri: string, entrystoreService: EntrystoreService, publisherEntry: Entry | null, + env: EnvSettings, ): Promise> { switch (pageType) { case "dataset": { @@ -263,7 +283,10 @@ export const EntrystoreProvider: FC = ({ return { relatedSpecifications: specs, - address: formatTerminologyAddress(resourceUri), + address: formatTerminologyAddress(resourceUri, [ + env.PRODUCTION_BASE_URL, + env.SANDBOX_BASE_URL, + ]), downloadFormats: formats, organisationLink, }; @@ -351,7 +374,11 @@ export const EntrystoreProvider: FC = ({ }; const termsEntrystoreService = EntrystoreService.getInstance({ - baseUrl: `https://${state.env.ENTRYSCAPE_TERMS_PATH}/store`, + baseUrl: `https://${ + entry.getEntryInfo().getMetadataURI().includes("sandbox") + ? "sandbox." + : "" + }editera.dataportal.se/store`, lang, t, }); diff --git a/types/global.d.ts b/types/global.d.ts index 19f407ed3..0d0c6fbed 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -65,6 +65,14 @@ export interface DataportalSettings { matomoSiteId: string; } +export type RedirectConfig = { + pathPrefix: string; + redirectPath: "/concepts" | "/specifications" | "/terminology"; + entrystorePathKey: "ENTRYSCAPE_TERMS_PATH" | "ENTRYSCAPE_SPECS_PATH"; + param?: string | string[]; + secondParam?: string; +}; + declare module "remark-gfm" { const content: unknown; export default content; diff --git a/types/search.d.ts b/types/search.d.ts index 8ac837148..9597bb52b 100644 --- a/types/search.d.ts +++ b/types/search.d.ts @@ -26,6 +26,7 @@ export interface HitSpecification { path?: string; titleResource?: string; descriptionResource?: string; + pathResolver?: (_entry: ESEntry) => string; } export interface FacetSpecification { diff --git a/utilities/check-lang.tsx b/utilities/check-lang.tsx index f182e4515..53fb751c2 100644 --- a/utilities/check-lang.tsx +++ b/utilities/check-lang.tsx @@ -1,5 +1,8 @@ +import { NextRouter } from "next/router"; import { ReactNode } from "react"; +import i18n from "@/i18n"; + export interface IHeading { lang: string; node: ReactNode; @@ -53,3 +56,23 @@ export const checkLang = (text: string | null) => { return text; } }; + +export const includeLangInPath = (lang: string) => { + return lang === i18n.defaultLocale ? "" : `/${lang}`; +}; + +export const handleLocale = ( + pathname: string, + currentLocale: string, + currentPath: string, + router: NextRouter, +) => { + if ( + currentLocale === i18n.defaultLocale && + pathname.startsWith(`/${currentLocale}/`) + ) { + router.replace(currentPath, undefined, { + shallow: true, + }); + } +}; diff --git a/utilities/entryscape/blocks/concept.ts b/utilities/entryscape/blocks/concept.ts index 1d8bf4555..3586838f0 100644 --- a/utilities/entryscape/blocks/concept.ts +++ b/utilities/entryscape/blocks/concept.ts @@ -2,7 +2,11 @@ import { Entry } from "@entryscape/entrystore-js"; import { Translate } from "next-translate"; -import { getLocalizedValue } from "@/utilities/entrystore/entrystore-helpers"; +import { includeLangInPath } from "@/utilities/check-lang"; +import { + conceptsPathResolver, + getLocalizedValue, +} from "@/utilities/entrystore/entrystore-helpers"; export const conceptBlocks = (t: Translate, iconSize: number, lang: string) => [ { @@ -38,20 +42,21 @@ export const conceptBlocks = (t: Translate, iconSize: number, lang: string) => [ block: "conceptLink", run: function (node: any, a2: any, a3: any, entry: Entry) { if (node && node.firstElementChild && entry) { + const baseUrl = window.location.origin; const el = document.createElement("a"); node.setAttribute("class", "entryscape"); node.firstElementChild.appendChild(el); - const contextId = entry.getContext().getId(); - const id = entry.getId(); const label = getLocalizedValue( entry.getAllMetadata(), "skos:prefLabel", ); el.innerHTML = label; - const uri = `/${lang}/concepts/${contextId}_${id}`; + const uri = `${baseUrl}${includeLangInPath(lang)}${conceptsPathResolver( + entry, + )}`; el.setAttribute("href", uri); } }, diff --git a/utilities/entryscape/blocks/global.ts b/utilities/entryscape/blocks/global.ts index ec0904210..03673e0a0 100644 --- a/utilities/entryscape/blocks/global.ts +++ b/utilities/entryscape/blocks/global.ts @@ -233,44 +233,3 @@ export const getApiExploreUrl = ( return `${currentPath}/apiexplore/${apientryid}`; }; - -export const hemvist = (t: Translate) => { - return { - block: "hemvist", - loadEntry: true, - run: function (node: any, data: any, items: any, entry: Entry) { - const currentPath = window.location.pathname; - const resourceURI = entry.getResourceURI(); - let linkTitle = t("pages|concept_page$concept_adress"); - - if (currentPath.includes("/terminology/")) - linkTitle = t("pages|concept_page$term_adress"); - - if (currentPath.includes("/specifications/")) - linkTitle = t("pages|specification_page$address"); - - if ( - resourceURI.indexOf("https://dataportal.se/") === 0 || - resourceURI.indexOf("https://www-sandbox.dataportal.se/") === 0 - ) { - node.innerHTML = - '

' + - linkTitle + - ':

' + - linkTitle + - '