diff --git a/.eslintrc.json b/.eslintrc.json index ce76c9697..2a2abe8f6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,18 @@ { - "extends": ["next/core-web-vitals"], - "plugins": ["prettier"], + "extends": [ + "next/core-web-vitals", + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript" + ], + "plugins": ["prettier", "import", "@typescript-eslint"], + "settings": { + "import/resolver": { + "typescript": true, + "node": true + } + }, "rules": { "no-console": ["error", { "allow": ["warn", "error"] }], "react-hooks/exhaustive-deps": "off", @@ -8,7 +20,27 @@ "cypress": "off", "comma-dangle": "off", "@typescript-eslint/comma-dangle": "off", - "no-unused-vars": 2, - "prettier/prettier": "error" + "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "prettier/prettier": "error", + "import/order": [ + "error", + { + "groups": [ + "builtin", // Built-in imports (come from NodeJS native) go first + "external", // <- External imports + "internal", // <- Absolute imports + ["sibling", "parent"], // <- Relative imports, the sibling and parent types they can be mingled together + "index", // <- index imports + "unknown" // <- unknown + ], + "newlines-between": "always", + "alphabetize": { + /* sort in ascending order. Options: ["ignore", "asc", "desc"] */ + "order": "asc", + /* ignore case. Options: [true, false] */ + "caseInsensitive": true + } + } + ] } } diff --git a/assets/icons/check-done.svg b/assets/icons/check-done.svg new file mode 100644 index 000000000..d562da369 --- /dev/null +++ b/assets/icons/check-done.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/chevron-down.svg b/assets/icons/chevron-down.svg new file mode 100644 index 000000000..f7043d8ea --- /dev/null +++ b/assets/icons/chevron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/chevron-up.svg b/assets/icons/chevron-up.svg new file mode 100644 index 000000000..391e06273 --- /dev/null +++ b/assets/icons/chevron-up.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/icons/chevronDown.svg b/assets/icons/chevronDown.svg deleted file mode 100644 index 8e103bca6..000000000 --- a/assets/icons/chevronDown.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/assets/icons/chevronUp.svg b/assets/icons/chevronUp.svg deleted file mode 100644 index 878409e91..000000000 --- a/assets/icons/chevronUp.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/assets/icons/closeCross.svg b/assets/icons/cross.svg similarity index 71% rename from assets/icons/closeCross.svg rename to assets/icons/cross.svg index 8c3aea470..d1de276fc 100644 --- a/assets/icons/closeCross.svg +++ b/assets/icons/cross.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/assets/icons/diamond.svg b/assets/icons/diamond.svg new file mode 100644 index 000000000..6c3674f9f --- /dev/null +++ b/assets/icons/diamond.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/external-link.svg b/assets/icons/external-link.svg index 3ce7fdacf..e041810ba 100644 --- a/assets/icons/external-link.svg +++ b/assets/icons/external-link.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/assets/icons/holdingHands.svg b/assets/icons/holdingHands.svg new file mode 100644 index 000000000..134c0b4d1 --- /dev/null +++ b/assets/icons/holdingHands.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/mail.svg b/assets/icons/mail.svg index c9e927e16..22af48858 100644 --- a/assets/icons/mail.svg +++ b/assets/icons/mail.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/assets/icons/specList.svg b/assets/icons/specList.svg new file mode 100644 index 000000000..4d7c764ab --- /dev/null +++ b/assets/icons/specList.svg @@ -0,0 +1,3 @@ + + + diff --git a/components/content/blocks/AccordionBlock/index.tsx b/components/blocks/accordion-block/index.tsx similarity index 89% rename from components/content/blocks/AccordionBlock/index.tsx rename to components/blocks/accordion-block/index.tsx index 41a4b2aea..873483992 100644 --- a/components/content/blocks/AccordionBlock/index.tsx +++ b/components/blocks/accordion-block/index.tsx @@ -1,10 +1,11 @@ +import useTranslation from "next-translate/useTranslation"; import { FC, useContext, useState } from "react"; -import { FaqFragment as IFaq } from "@/graphql/__generated__/operations"; -import { HtmlParser } from "@/components/global/Typography/HtmlParser"; -import CloseIcon from "@/assets/icons/closeCross.svg"; + +import CrossIcon from "@/assets/icons/cross.svg"; import PlusIcon from "@/assets/icons/plus.svg"; -import useTranslation from "next-translate/useTranslation"; -import { SettingsContext } from "@/providers/SettingsProvider"; +import { HtmlParser } from "@/components/typography/html-parser"; +import { FaqFragment as IFaq } from "@/graphql/__generated__/operations"; +import { SettingsContext } from "@/providers/settings-provider"; interface AccordionBlockProps extends IFaq { idx: number; @@ -37,7 +38,7 @@ export const AccordionBlock: FC = ({ {question} {open ? ( - { ); }; -export const BlockList: React.FC = ({ +export const BlockList: FC = ({ blocks, className, landingPage, @@ -98,7 +100,7 @@ export const BlockList: React.FC = ({ landingPage={landingPage} /> ); - case "dataportal_Digg_ModuleList": + case "dataportal_Digg_ModuleList": { const typedBlock = block as ModuleListDataFragment; return ( typedBlock.modules && @@ -106,6 +108,7 @@ export const BlockList: React.FC = ({ )) ); + } case "dataportal_Digg_FormBlock": return ( = ({ __typename="dataportal_Digg_Form" /> ); - default: + default: { + const unknownBlock = block as { __typename: string; id: string }; return ( -
+

- <>{(block as any)?.__typename} Not found + <>{unknownBlock.__typename} Not found

-
{JSON.stringify(block, null, 2)}
+
{JSON.stringify(unknownBlock, null, 2)}
); + } } })}
diff --git a/components/content/blocks/MediaBlock/index.tsx b/components/blocks/media-block/index.tsx similarity index 83% rename from components/content/blocks/MediaBlock/index.tsx rename to components/blocks/media-block/index.tsx index 563d7f9f0..2be777c7c 100644 --- a/components/content/blocks/MediaBlock/index.tsx +++ b/components/blocks/media-block/index.tsx @@ -1,13 +1,16 @@ -import { MediaFragment } from "@/graphql/__generated__/operations"; -import { checkLang } from "@/utilities"; import env from "@beam-australia/react-env"; -import { MediaBaseFragment } from "@/graphql/__generated__/operations"; -import { isExternalLink } from "@/utilities"; -import { CustomImage } from "@/components/global/CustomImage"; -import { Heading } from "@/components/global/Typography/Heading"; import Link from "next/link"; +import { FC } from "react"; + import DocumentIcon from "@/assets/icons/document.svg"; import PDFIcon from "@/assets/icons/PDF.svg"; +import { CustomImage } from "@/components/custom-image"; +import { Heading } from "@/components/typography/heading"; +import { + MediaBaseFragment, + MediaFragment, +} from "@/graphql/__generated__/operations"; +import { isExternalLink, checkLang } from "@/utilities"; export const handleUrl = ({ screen9, url, __typename }: MediaBaseFragment) => { const documentBaseUrl = env("DOCUMENT_BASE_URL"); @@ -67,7 +70,7 @@ const renderMedia = ( } }; -export const MediaBlock: React.FC = ({ +export const MediaBlock: FC = ({ heading, description, media, diff --git a/components/content/blocks/PromotedContentBlock/index.tsx b/components/blocks/promoted-content-block/index.tsx similarity index 88% rename from components/content/blocks/PromotedContentBlock/index.tsx rename to components/blocks/promoted-content-block/index.tsx index f8d03eb48..cff8af89f 100644 --- a/components/content/blocks/PromotedContentBlock/index.tsx +++ b/components/blocks/promoted-content-block/index.tsx @@ -1,10 +1,12 @@ -import { FC } from "react"; -import { Heading } from "@/components/global/Typography/Heading"; import useTranslation from "next-translate/useTranslation"; -import { PromotedContentFragment } from "@/graphql/__generated__/operations"; -import { CustomImage } from "@/components/global/CustomImage"; -import { ButtonLink } from "@/components/global/Button"; +import { FC } from "react"; + import Arrow from "@/assets/icons/arrowRight.svg"; +import { ButtonLink } from "@/components/button"; +import { CustomImage } from "@/components/custom-image"; +import { Heading } from "@/components/typography/heading"; +import { PromotedContentFragment } from "@/graphql/__generated__/operations"; + export const PromotedContentBlock: FC = ({ heading, externalLink, diff --git a/components/content/blocks/QuoteBlock/index.tsx b/components/blocks/quote-block/index.tsx similarity index 94% rename from components/content/blocks/QuoteBlock/index.tsx rename to components/blocks/quote-block/index.tsx index d850d9fa0..d7831b255 100644 --- a/components/content/blocks/QuoteBlock/index.tsx +++ b/components/blocks/quote-block/index.tsx @@ -1,7 +1,8 @@ import { FC } from "react"; + import QuoteIcon from "@/assets/icons/quote.svg"; +import { CustomImage } from "@/components/custom-image"; import { QuoteFragment } from "@/graphql/__generated__/operations"; -import { CustomImage } from "@/components/global/CustomImage"; export const QuoteBlock: FC = ({ quote, author, image }) => { return ( diff --git a/components/content/blocks/RelatedContentBlock/index.tsx b/components/blocks/related-content-block/index.tsx similarity index 89% rename from components/content/blocks/RelatedContentBlock/index.tsx rename to components/blocks/related-content-block/index.tsx index 5045bd996..73fea12ed 100644 --- a/components/content/blocks/RelatedContentBlock/index.tsx +++ b/components/blocks/related-content-block/index.tsx @@ -1,8 +1,9 @@ -import { FC } from "react"; -import { PromoProps, Promo } from "@/components/content/Promo"; -import { ButtonLink } from "@/components/global/Button"; -import { Heading } from "@/components/global/Typography/Heading"; import useTranslation from "next-translate/useTranslation"; +import { FC } from "react"; + +import { ButtonLink } from "@/components/button"; +import { PromoProps, Promo } from "@/components/promo"; +import { Heading } from "@/components/typography/heading"; import { RelatedContentFragment } from "@/graphql/__generated__/operations"; interface RelatedContentProps extends RelatedContentFragment { diff --git a/components/content/blocks/TextBlock/index.tsx b/components/blocks/text-block/index.tsx similarity index 77% rename from components/content/blocks/TextBlock/index.tsx rename to components/blocks/text-block/index.tsx index a6028906e..6e6feee94 100644 --- a/components/content/blocks/TextBlock/index.tsx +++ b/components/blocks/text-block/index.tsx @@ -1,7 +1,8 @@ import { FC } from "react"; + +import { Heading } from "@/components/typography/heading"; +import { HtmlParser } from "@/components/typography/html-parser"; import { TextFragment } from "@/graphql/__generated__/operations"; -import { Heading } from "@/components/global/Typography/Heading"; -import { HtmlParser } from "@/components/global/Typography/HtmlParser"; export const TextBlock: FC = ({ heading, text }) => { return ( diff --git a/components/content/blocks/VideoBlock/index.tsx b/components/blocks/video-block/index.tsx similarity index 77% rename from components/content/blocks/VideoBlock/index.tsx rename to components/blocks/video-block/index.tsx index 46792bbf5..7a952623f 100644 --- a/components/content/blocks/VideoBlock/index.tsx +++ b/components/blocks/video-block/index.tsx @@ -1,9 +1,11 @@ +import { FC } from "react"; + +import { Heading } from "@/components/typography/heading"; +import { VideoPlayer } from "@/components/video-player"; import { VideoFragment } from "@/graphql/__generated__/operations"; import { checkLang } from "@/utilities"; -import { Heading } from "@/components/global/Typography/Heading"; -import { VideoPlayer } from "@/components/global/VideoPlayer"; -export const VideoBlock: React.FC = ({ +export const VideoBlock: FC = ({ heading, description, video_id, diff --git a/components/global/Button/index.tsx b/components/button/index.tsx similarity index 64% rename from components/global/Button/index.tsx rename to components/button/index.tsx index f3f2095e8..0a16818b8 100644 --- a/components/global/Button/index.tsx +++ b/components/button/index.tsx @@ -1,3 +1,5 @@ +import { cx, cva, VariantProps } from "class-variance-authority"; +import Link from "next/link"; import { FC, PropsWithChildren, @@ -5,9 +7,9 @@ import { AnchorHTMLAttributes, useContext, } from "react"; -import { cx, cva, VariantProps } from "class-variance-authority"; -import Link from "next/link"; -import { SettingsContext } from "@/providers/SettingsProvider"; + +import { SettingsContext } from "@/providers/settings-provider"; +import { AddIcon } from "@/types/global"; const buttonVariants = cva(["button"], { variants: { @@ -19,6 +21,7 @@ const buttonVariants = cva(["button"], { variant: { primary: ["button--primary"], secondary: ["button--secondary"], + light: ["button--light"], plain: ["button--plain"], pink: ["button--pink"], filter: ["button--filter"], @@ -31,30 +34,61 @@ const buttonVariants = cva(["button"], { }); type IconLabelProps = { - icon?: any; + icon?: AddIcon; size?: "xs" | "sm" | "lg"; label?: string; iconPosition?: "left" | "right"; + iconSizeBase?: number; }; -const IconLabel: FC = ({ icon, label, size, iconPosition }) => { +const IconLabel: FC = ({ + icon, + label, + size, + iconPosition, + iconSizeBase, +}) => { const Icon = icon; - const { iconSize } = useContext(SettingsContext); + const { iconSize: contextIconSize } = useContext(SettingsContext); return ( <> - {iconPosition === "left" && ( + {iconPosition === "left" && Icon && ( )} {label && {label}} - {iconPosition === "right" && ( + {iconPosition === "right" && Icon && ( )} @@ -63,8 +97,9 @@ const IconLabel: FC = ({ icon, label, size, iconPosition }) => { }; type ButtonProps = VariantProps & { - icon?: any; + icon?: AddIcon; iconPosition?: "left" | "right"; + iconSize?: number; label?: string; }; @@ -76,6 +111,7 @@ const Button: FC< className, icon, iconPosition, + iconSize, label, children, ...rest @@ -88,6 +124,7 @@ const Button: FC< > & { - icon?: any; + icon?: AddIcon; iconPosition?: "left" | "right"; label?: string; href: string; diff --git a/components/content/ContentBox/index.tsx b/components/content-box/index.tsx similarity index 89% rename from components/content/ContentBox/index.tsx rename to components/content-box/index.tsx index 19ab2fde6..809e09c63 100644 --- a/components/content/ContentBox/index.tsx +++ b/components/content-box/index.tsx @@ -1,5 +1,6 @@ import { FC, PropsWithChildren } from "react"; -import { Heading } from "@/components/global/Typography/Heading"; + +import { Heading } from "@/components/typography/heading"; type ContentBoxProps = { heading: string; diff --git a/components/content/Search/SearchFilters/index.tsx b/components/content/Search/SearchFilters/index.tsx deleted file mode 100644 index 76bf063c3..000000000 --- a/components/content/Search/SearchFilters/index.tsx +++ /dev/null @@ -1,518 +0,0 @@ -import React, { useContext, useState, useMemo } from "react"; -import { ESRdfType, ESType } from "@/utilities/entryScape"; -import useTranslation from "next-translate/useTranslation"; -import { SearchContextData } from "@/providers/SearchProvider"; -import { SearchFilter } from "@/components/content/Search/SearchFilters/SearchFilter"; -import FilterIcon from "@/assets/icons/filter.svg"; -import SearchIcon from "@/assets/icons/search.svg"; -import { Button } from "@/components/global/Button"; -import { TextInput } from "@/components/global/Form/TextInput"; -import { SettingsContext } from "@/providers/SettingsProvider"; -import { - SearchCheckboxFilter, - SearchCheckboxFilterIcon, -} from "./SearchCheckboxFilter/SearchCheckboxFilter"; -import { SearchActiveFilters } from "./SearchActiveFilters"; - -interface SearchFilterProps { - showFilter: boolean; - search: SearchContextData; - searchMode: SearchMode; - query: string; - setShowFilter: React.Dispatch>; - showTip?: boolean; -} - -interface MarkAllProps { - search: SearchContextData; - toggleKey: string; - title: string; -} - -interface FilterSearchProps { - filterKey: string; - filter: InputFilter; - setFilter: React.Dispatch>; - title: string; - fetchMore: () => void; -} - -type InputFilter = { [key: string]: string }; -export type SearchMode = "content" | "datasets" | "concepts" | "specifications"; - -const FilterSearch: React.FC = ({ - filterKey, - title, - filter, - setFilter, - fetchMore, -}) => { - const { t } = useTranslation("pages"); - - const clearCurrentScrollPos = () => { - if (typeof localStorage != "undefined" && typeof location != "undefined") { - localStorage.setItem(`ScrollposY_${location.search}`, "0"); - } - }; - - return ( -
- ( - clearCurrentScrollPos(), - fetchMore(), - setFilter({ ...filter, [filterKey]: e.target.value }) - )} - /> -
- ); -}; - -const MarkAll: React.FC = ({ search, toggleKey, title }) => { - return ( -
- -
- ); -}; - -const FindFilters = ( - categoryFilters: SearchFacetValue[], - checkedFilters: SearchFacetValue[] | undefined, -) => { - if (!checkedFilters) return ""; - - const allTitles = categoryFilters.map((item) => item.title); - const checkedTitles = checkedFilters.map((item) => item.title); - const union = allTitles.filter((x) => checkedTitles.includes(x)); - - if (union.length > 0) { - return " (" + union.length + ")"; - } - - return ""; -}; - -/** - * Controls for filtering searchhits - * - * @param {boolean} showFilter disable or enable filters - * @param {SearchContextData} search context for handling searchstate - * @param {SearchMode} searchMode - * @param {string} query - * @param setShowFilter - * @returns JSX-elements of selects and checkboxes - */ -export const SearchFilters: React.FC = ({ - showFilter, - search, - searchMode, - query, - setShowFilter, -}) => { - const { t } = useTranslation(); - const { iconSize } = useContext(SettingsContext); - const [inputFilter, setInputFilter] = useState({}); - - const clearCurrentScrollPos = () => { - if (typeof localStorage != "undefined" && typeof location != "undefined") { - localStorage.setItem(`ScrollposY_${location.search}`, "0"); - } - }; - - const selected = (key: string, facetValue: SearchFacetValue) => { - return search.facetSelected(key, facetValue.resource); - }; - - const doSearch = async (key: string, facetValue: SearchFacetValue) => { - clearCurrentScrollPos(); - - await search.toggleFacet(facetValue); - - if (search.facetSelected(key, "*")) { - let wildcardFacet: SearchFacetValue = { - count: -1, - facet: key, - facetType: ESType.wildcard, - related: false, - facetValueString: "", - resource: "*", - title: t(`filters|allchecktext$${key}`), - }; - await search.toggleFacet(wildcardFacet); - } - - await search.doSearch(false, true, false); - - if (selected(key, facetValue)) { - search.sortAllFacets(key); - } else { - search.sortAllFacets(); - } - }; - - function updateFilters() { - search.updateFacetStats(); - setShowFilter(!showFilter); - } - - const groupedFacets = useMemo(() => { - const grouped: { [key: string]: { [key: string]: SearchFacet } } = {}; - - Object.entries(search.allFacets || {}).forEach(([key, facet]) => { - const group = facet.group || "default"; - if (!grouped[group]) { - grouped[group] = {}; - } - grouped[group][key] = facet; - }); - - return grouped; - }, [search.allFacets]); - - const hvd = "http://data.europa.eu/r5r/applicableLegislation"; - const national = "http://purl.org/dc/terms/subject"; - - const activeCheckboxFilters = useMemo(() => { - const filters = []; - - // HVD filter - if (search.request.facetValues?.some((t) => t.title === ESRdfType.hvd)) { - filters.push({ - id: "hvd_only", - label: t(`resources|${hvd}`), - facetValue: search.request.facetValues.find( - (t) => t.title === ESRdfType.hvd, - ), - }); - } - - // National filter - if ( - search.request.facetValues?.some( - (t) => t.facet === ESRdfType.national_data, - ) - ) { - filters.push({ - id: "national_only", - label: t(`resources|${national}`), - facetValue: search.request.facetValues.find( - (t) => t.facet === ESRdfType.national_data, - ), - }); - } - - // API only filter - if ( - searchMode === "datasets" && - search.request.esRdfTypes?.some( - (t) => t === ESRdfType.esterms_ServedByDataService, - ) && - search.request.esRdfTypes?.some( - (t) => t === ESRdfType.esterms_IndependentDataService, - ) && - !search.request.esRdfTypes?.some((t) => t === ESRdfType.dataset) - ) { - filters.push({ - id: "api_only", - label: t(`resources|api`), - // Special handling for API filter since it uses esRdfTypes - isApiFilter: true, - }); - } - - return filters; - }, [search.request.facetValues, search.request.esRdfTypes, searchMode]); - - return ( -
- - - ), - )} - - - {value.facetValues.length > value.show && ( -
- - - ); - } else if (key === hvd) { - return ( - filter.id === "hvd_only", - )} - onChange={() => doSearch(key, facetValues[0])} - label={t(`resources|${key}`)} - iconSize={iconSize} - /> - ); - } else if (key === national) { - return ( - filter.id === "national_only", - )} - onChange={() => doSearch(key, facetValues[0])} - label={t(`resources|${key}`)} - iconSize={iconSize} - /> - ); - } - })} - - {searchMode == "datasets" && groupName == "distribution" && ( - filter.id === "api_only", - )} - onChange={() => { - clearCurrentScrollPos(); - if ( - activeCheckboxFilters.some( - (filter) => filter.id === "api_only", - ) - ) { - search - .set({ - esRdfTypes: [ - ESRdfType.dataset, - ESRdfType.esterms_IndependentDataService, - ESRdfType.esterms_ServedByDataService, - ], - query: query, - }) - .then(() => search.doSearch()); - } else { - search - .set({ - esRdfTypes: [ - ESRdfType.esterms_IndependentDataService, - ESRdfType.esterms_ServedByDataService, - ], - query: query, - }) - .then(() => search.doSearch()); - } - }} - label={t(`resources|api`)} - iconSize={iconSize} - /> - )} - - - ))} - - - - - ); -}; - -export default SearchFilters; diff --git a/components/content/Search/SearchPageSelector/index.tsx b/components/content/Search/SearchPageSelector/index.tsx deleted file mode 100644 index cccd02238..000000000 --- a/components/content/Search/SearchPageSelector/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import useTranslation from "next-translate/useTranslation"; -import { useRouter } from "next/router"; -import React from "react"; -import { ButtonLink } from "@/components/global/Button"; - -interface SearchTabsProps { - query?: string; -} - -//Navigation between data & Api:s, concepts, specifications. -export const SearchPageSelector: React.FC = ({ query }) => { - const { t, lang } = useTranslation("pages"); - const { pathname } = useRouter() || {}; - return ( - - ); -}; diff --git a/components/content/Statistic/StatisticGraphNumbers/index.tsx b/components/content/Statistic/StatisticGraphNumbers/index.tsx deleted file mode 100644 index 6618c405d..000000000 --- a/components/content/Statistic/StatisticGraphNumbers/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import useTranslation from "next-translate/useTranslation"; -import React, { useContext, useEffect, useState } from "react"; -import { SettingsContext } from "@/providers/SettingsProvider"; -import { StatisticListItemHistory } from "@/components/content/Statistic/StatisticListItemHistory"; -import { Heading } from "@/components/global/Typography/Heading"; - -// import { isIE } from 'react-device-detect'; - -interface StatisticState { - children?: React.ReactNode; - x?: any; - y?: any; - xList: string[]; - yList: string[]; - - toSort?: any; - topItemsToShow: number; - screenWidth: number; -} - -export const StatisticGraphNumbers: React.FC = () => { - const { env } = useContext(SettingsContext); - const { t } = useTranslation("pages"); - - const [stats, setStats] = useState({ - x: [], - y: [], - xList: [], - yList: [], - toSort: [], - topItemsToShow: 19, - screenWidth: 1080, - }); - - useEffect(() => { - if (typeof fetch !== "undefined") { - fetch( - env.ENTRYSCAPE_HISTORY_STATS_URL - ? env.ENTRYSCAPE_HISTORY_STATS_URL - : "https://admin.dataportal.se/stats/historyData.json", - ) - .then((response) => response.json()) - .then((data) => { - for (let i = 0; i < data.length; i++) { - let item = { - x: data[i].x.toString().substring(0, 7), - y: data[i].y, - }; - - if (i < 19) { - stats.toSort.push(item); - // list.push(item); - } - } - - setStats((prev) => { - return { - ...prev, - xList: stats.toSort.map((item: any) => item.x), - yList: stats.toSort.map((item: any) => item.y), - }; - }); - }); - } - }, []); - - if (/* isIE do we still need this? */ false) { - return <>; - } else { - return ( -
- - {t("statistic$dataset-numbers")} - -
-
    - {stats.yList && - stats.yList - .slice(0, stats.topItemsToShow) - .map((item: any, index: any) => { - return ( - - ); - })} -
-
-
- ); - } -}; diff --git a/components/custom-image/index.tsx b/components/custom-image/index.tsx new file mode 100644 index 000000000..a96fb4ca4 --- /dev/null +++ b/components/custom-image/index.tsx @@ -0,0 +1,104 @@ +import env from "@beam-australia/react-env"; +import Image from "next/image"; +import { FC } from "react"; + +import noImage from "@/assets/logos/noImage.png"; +import { ImageFragment as ImageInterface } from "@/graphql/__generated__/operations"; +import { isExternalLink } from "@/utilities"; + +/** + * Props for the CustomImage component + * @interface CustomImageProps + */ +interface CustomImageProps { + /** The image object containing url, width, height, and alt text */ + image: ImageInterface | null; + /** Responsive image sizes attribute */ + sizes?: string; + /** Additional CSS classes */ + className?: string; + /** Whether to prioritize image loading */ + priority?: boolean; + /** Desired image width */ + width?: number; + /** Image quality (1-100) */ + quality?: number; +} + +/** Default dimensions and quality settings */ +const DEFAULT_WIDTH = 384; +const DEFAULT_HEIGHT = 200; +const DEFAULT_QUALITY = 75; +const DEFAULT_SIZES = + "(max-width: 640px) 100vw, (max-width: 1200px) 50vw, 20vw"; + +/** + * Checks if the image URL is a Next.js static asset + * @param url - The image URL to check + * @returns boolean indicating if the URL is a Next.js static asset + */ +const isNextStatic = (url: string) => !url.startsWith("/uploads"); + +/** + * Generates the complete image URL with width and quality parameters + * @param imageUrl - The base image URL + * @param width - Desired image width + * @param quality - Desired image quality + * @returns The complete image URL with parameters + */ +const getImageUrl = (imageUrl: string, width: number, quality: number) => { + if (isExternalLink(imageUrl)) return imageUrl; + const baseUrl = env("MEDIA_BASE_URL") || ""; + return `${baseUrl}${imageUrl}?w=${width}&q=${quality}`; +}; + +/** + * A custom Image component that handles various image sources and formats + * Supports external images, Next.js static assets, and dynamic images with optimization + * Falls back to a placeholder image when no image is provided + */ +export const CustomImage: FC = ({ + image, + sizes = DEFAULT_SIZES, + className = "", + priority = false, + width = DEFAULT_WIDTH, + quality = DEFAULT_QUALITY, +}) => { + if (!image) { + return ( + image not found + ); + } + + const imageProps = { + width: Number(image.width) || DEFAULT_WIDTH, + height: Number(image.height) || DEFAULT_HEIGHT, + className, + alt: image.alt || "", + sizes, + priority, + }; + + if (isNextStatic(image.url)) { + // eslint-disable-next-line jsx-a11y/alt-text + return ; + } + + return ( + // eslint-disable-next-line jsx-a11y/alt-text + + ); +}; diff --git a/components/global/CustomLink/index.tsx b/components/custom-link/index.tsx similarity index 95% rename from components/global/CustomLink/index.tsx rename to components/custom-link/index.tsx index c700fb70c..eb058e9cf 100644 --- a/components/global/CustomLink/index.tsx +++ b/components/custom-link/index.tsx @@ -1,11 +1,12 @@ -import { isMailLink } from "@/utilities"; import { cx } from "class-variance-authority"; import Link from "next/link"; +import useTranslation from "next-translate/useTranslation"; import { FC, LinkHTMLAttributes, PropsWithChildren, useContext } from "react"; -import MailIcon from "@/assets/icons/mail.svg"; + import ExternalLinkIcon from "@/assets/icons/external-link.svg"; -import useTranslation from "next-translate/useTranslation"; -import { SettingsContext } from "@/providers/SettingsProvider"; +import MailIcon from "@/assets/icons/mail.svg"; +import { SettingsContext } from "@/providers/settings-provider"; +import { isMailLink } from "@/utilities"; type CustomLinkProps = { href: string; diff --git a/components/global/FileFormatBadge/index.tsx b/components/file-format-badge/index.tsx similarity index 100% rename from components/global/FileFormatBadge/index.tsx rename to components/file-format-badge/index.tsx diff --git a/components/global/Form/FormGeneratePDF/index.tsx b/components/form/form-generate-pdf/index.tsx similarity index 90% rename from components/global/Form/FormGeneratePDF/index.tsx rename to components/form/form-generate-pdf/index.tsx index 2ecea3332..4297db97c 100644 --- a/components/global/Form/FormGeneratePDF/index.tsx +++ b/components/form/form-generate-pdf/index.tsx @@ -1,13 +1,14 @@ -import { FC, useRef, useState } from "react"; import { usePathname } from "next/navigation"; import useTranslation from "next-translate/useTranslation"; +import { FC, useRef, useState } from "react"; + +import { BlockList } from "@/components/blocks/block-list"; +import { Button } from "@/components/button"; import { ModuleDataFragment } from "@/graphql/__generated__/operations"; -import { BlockList } from "@/components/content/blocks/BlockList"; -import { Button } from "@/components/global/Button"; import { FormTypes } from "@/types/form"; -import { GeneratePDF } from "@/utilities/formUtils"; +import { GeneratePDF } from "@/utilities/form-utils"; -import { Modal } from "../../Modal"; +import { Modal } from "../../modal"; type Props = { formDataArray: FormTypes[][]; diff --git a/components/global/Form/Label/index.tsx b/components/form/label/index.tsx similarity index 55% rename from components/global/Form/Label/index.tsx rename to components/form/label/index.tsx index a9319c00d..4c617cbab 100644 --- a/components/global/Form/Label/index.tsx +++ b/components/form/label/index.tsx @@ -1,12 +1,8 @@ import { FC, LabelHTMLAttributes, PropsWithChildren } from "react"; -interface LabelProps extends LabelHTMLAttributes {} - -export const Label: FC> = ({ - children, - className, - ...props -}) => ( +export const Label: FC< + PropsWithChildren> +> = ({ children, className, ...props }) => (