From ee2a71643b48318ff00e93763cb9d4233b493575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Fri, 11 Oct 2024 13:17:27 +0200 Subject: [PATCH] Add terrestrial data disclaimer --- .../terrestrial-data-disclaimer-dialog.tsx | 52 +++++++++++++++++++ .../src/components/tooltip-button/index.tsx | 13 +++-- frontend/src/components/ui/dialog.tsx | 34 ++++++------ frontend/src/components/widget/index.tsx | 8 ++- .../terrestrial-conservation/index.tsx | 19 +++++++ frontend/src/containers/map/store.ts | 10 +++- frontend/src/layouts/map.tsx | 20 ++++++- .../pages/progress-tracker/[locationCode].tsx | 1 + frontend/src/styles/icons/notification.svg | 3 ++ frontend/tailwind.config.js | 1 + frontend/translations/en.json | 13 +++-- 11 files changed, 145 insertions(+), 29 deletions(-) create mode 100644 frontend/src/components/terrestrial-data-disclaimer-dialog.tsx create mode 100644 frontend/src/styles/icons/notification.svg diff --git a/frontend/src/components/terrestrial-data-disclaimer-dialog.tsx b/frontend/src/components/terrestrial-data-disclaimer-dialog.tsx new file mode 100644 index 00000000..34125427 --- /dev/null +++ b/frontend/src/components/terrestrial-data-disclaimer-dialog.tsx @@ -0,0 +1,52 @@ +import { useAtom } from 'jotai'; +import { useTranslations } from 'next-intl'; + +import { Button } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import Icon from '@/components/ui/icon'; +import { terrestrialDataDisclaimerDialogAtom } from '@/containers/map/store'; +import Notification from '@/styles/icons/notification.svg'; + +interface TerrestrialDataDisclaimerDialogProps {} + +const TerrestrialDataDisclaimerDialog = ({}: TerrestrialDataDisclaimerDialogProps) => { + const t = useTranslations('pages.progress-tracker'); + const [, setOpen] = useAtom(terrestrialDataDisclaimerDialogAtom); + + return ( + + + + + {t('important-notification')} + + + {t('terrestrial-data-disclaimer-dialog-title')} + + + {t.rich('terrestrial-data-disclaimer-dialog-content', { + br: () =>
, + })} +
+ + + +
+
+ ); +}; + +export default TerrestrialDataDisclaimerDialog; diff --git a/frontend/src/components/tooltip-button/index.tsx b/frontend/src/components/tooltip-button/index.tsx index fa02b8b1..fa12d510 100644 --- a/frontend/src/components/tooltip-button/index.tsx +++ b/frontend/src/components/tooltip-button/index.tsx @@ -1,4 +1,4 @@ -import { Fragment, useState } from 'react'; +import { Fragment, ReactNode, useState } from 'react'; import Linkify from 'react-linkify'; @@ -20,9 +20,15 @@ interface TooltipButtonProps { className?: string; text: string; sources?: Source | Source[]; + extraContent?: ReactNode; } -const TooltipButton: FCWithMessages = ({ className, text, sources }) => { +const TooltipButton: FCWithMessages = ({ + className, + text, + sources, + extraContent, +}) => { const t = useTranslations('components.tooltip-button'); const [isTooltipOpen, setIsTooltipOpen] = useState(false); @@ -65,7 +71,7 @@ const TooltipButton: FCWithMessages = ({ className, text, so {Array.isArray(sources) && (
- Data sources: + {t('data-sources:')} {sources.map(({ id, title, url }, index) => ( = ({ className, text, so {t('data-source')} )} + {extraContent} ); diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx index 38061251..acec9b10 100644 --- a/frontend/src/components/ui/dialog.tsx +++ b/frontend/src/components/ui/dialog.tsx @@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef< , - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( + React.ComponentPropsWithoutRef & { + closable?: boolean; + } +>(({ className, children, closable = true, ...props }, ref) => ( {children} - - - Close - + {closable && ( + + + Close + + )} )); @@ -60,7 +64,7 @@ DialogHeader.displayName = 'DialogHeader'; const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
); @@ -70,11 +74,7 @@ const DialogTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); DialogTitle.displayName = DialogPrimitive.Title.displayName; @@ -82,11 +82,7 @@ const DialogDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); DialogDescription.displayName = DialogPrimitive.Description.displayName; diff --git a/frontend/src/components/widget/index.tsx b/frontend/src/components/widget/index.tsx index 21df360b..8d4aaf4a 100644 --- a/frontend/src/components/widget/index.tsx +++ b/frontend/src/components/widget/index.tsx @@ -1,4 +1,4 @@ -import { ComponentProps, PropsWithChildren, useMemo } from 'react'; +import { ComponentProps, PropsWithChildren, ReactNode, useMemo } from 'react'; import { timeFormatLocale } from 'd3-time-format'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -31,6 +31,7 @@ type WidgetProps = { errorMessage?: ComponentProps['message']; info?: ComponentProps['text']; sources?: ComponentProps['sources']; + tooltipExtraContent?: ReactNode; }; const d3Locales = { @@ -50,6 +51,7 @@ const Widget: FCWithMessages> = ({ errorMessage = undefined, info, sources, + tooltipExtraContent, children, }) => { const t = useTranslations('components.widget'); @@ -69,7 +71,9 @@ const Widget: FCWithMessages> = ({
{title &&

{title}

} - {(info || sources) && } + {(info || sources) && ( + + )}
{!showNoData && lastUpdated && ( {t('updated-on', { date: formattedLastUpdated })} diff --git a/frontend/src/containers/map/sidebar/main-panel/panels/details/widgets/terrestrial-conservation/index.tsx b/frontend/src/containers/map/sidebar/main-panel/panels/details/widgets/terrestrial-conservation/index.tsx index 4c999806..4825f9b6 100644 --- a/frontend/src/containers/map/sidebar/main-panel/panels/details/widgets/terrestrial-conservation/index.tsx +++ b/frontend/src/containers/map/sidebar/main-panel/panels/details/widgets/terrestrial-conservation/index.tsx @@ -1,14 +1,18 @@ import { useMemo } from 'react'; +import { useAtom } from 'jotai'; import { groupBy } from 'lodash-es'; import { useLocale, useTranslations } from 'next-intl'; import ConservationChart from '@/components/charts/conservation-chart'; import { Button } from '@/components/ui/button'; +import Icon from '@/components/ui/icon'; import Widget from '@/components/widget'; +import { terrestrialDataDisclaimerDialogAtom } from '@/containers/map/store'; import { useSyncMapContentSettings } from '@/containers/map/sync-settings'; import { formatKM } from '@/lib/utils/formats'; import { formatPercentage } from '@/lib/utils/formats'; +import Notification from '@/styles/icons/notification.svg'; import { FCWithMessages } from '@/types'; import { useGetDataInfos } from '@/types/generated/data-info'; import { useGetProtectionCoverageStats } from '@/types/generated/protection-coverage-stat'; @@ -29,6 +33,8 @@ const TerrestrialConservationWidget: FCWithMessages( @@ -163,6 +169,19 @@ const TerrestrialConservationWidget: FCWithMessages setDisclaimerDialogOpen(true)} + > + + + {t('data-disclaimer')} + + } > {stats && (
diff --git a/frontend/src/containers/map/store.ts b/frontend/src/containers/map/store.ts index 87ed12c4..d157f111 100644 --- a/frontend/src/containers/map/store.ts +++ b/frontend/src/containers/map/store.ts @@ -2,7 +2,7 @@ import { MapLayerMouseEvent } from 'react-map-gl'; import { Feature } from 'geojson'; import { atom } from 'jotai'; -import { atomWithReset } from 'jotai/utils'; +import { atomWithReset, atomWithStorage } from 'jotai/utils'; import { CustomMapProps } from '@/components/map/types'; import { LayerResponseDataObject } from '@/types/generated/strapi.schemas'; @@ -40,3 +40,11 @@ export const modellingAtom = atomWithReset<{ data: null, errorMessage: undefined, }); + +/** + * Whether the disclaimer dialog should be visible + */ +export const terrestrialDataDisclaimerDialogAtom = atomWithStorage( + 'terrestrial-data-disclaimer-dialog', + true +); diff --git a/frontend/src/layouts/map.tsx b/frontend/src/layouts/map.tsx index 998e6850..f23f9d15 100644 --- a/frontend/src/layouts/map.tsx +++ b/frontend/src/layouts/map.tsx @@ -1,5 +1,8 @@ import { PropsWithChildren, useEffect } from 'react'; +import dynamic from 'next/dynamic'; + +import { useAtomValue } from 'jotai'; import { useResetAtom } from 'jotai/utils'; import { useTranslations } from 'next-intl'; @@ -7,9 +10,20 @@ import Head from '@/components/head'; import Header from '@/components/header'; import Content from '@/containers/map/content'; import Sidebar from '@/containers/map/sidebar'; -import { drawStateAtom, modellingAtom } from '@/containers/map/store'; +import { + drawStateAtom, + modellingAtom, + terrestrialDataDisclaimerDialogAtom, +} from '@/containers/map/store'; import { FCWithMessages } from '@/types'; +const TerrestrialDataDisclaimerDialog = dynamic( + () => import('@/components/terrestrial-data-disclaimer-dialog'), + { + ssr: false, + } +); + const LAYOUT_TYPES = { progress_tracker: 'progress-tracker', conservation_builder: 'conservation-builder', @@ -30,6 +44,7 @@ const MapLayout: FCWithMessages> = ({ const resetModelling = useResetAtom(modellingAtom); const resetDrawState = useResetAtom(drawStateAtom); + const terrestrialDataDisclaimerDialogOpen = useAtomValue(terrestrialDataDisclaimerDialogAtom); useEffect(() => { if (type !== LAYOUT_TYPES.conservation_builder) { @@ -48,6 +63,9 @@ const MapLayout: FCWithMessages> = ({ } description={description} /> + {type === LAYOUT_TYPES.progress_tracker && terrestrialDataDisclaimerDialogOpen && ( + + )}
diff --git a/frontend/src/pages/progress-tracker/[locationCode].tsx b/frontend/src/pages/progress-tracker/[locationCode].tsx index 1b23f4f6..582b2571 100644 --- a/frontend/src/pages/progress-tracker/[locationCode].tsx +++ b/frontend/src/pages/progress-tracker/[locationCode].tsx @@ -30,6 +30,7 @@ ProgressTrackerPage.layout = { return { title: location?.[locationNameField], + type: 'progress-tracker', }; }, }; diff --git a/frontend/src/styles/icons/notification.svg b/frontend/src/styles/icons/notification.svg new file mode 100644 index 00000000..189d5271 --- /dev/null +++ b/frontend/src/styles/icons/notification.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 0f1fa166..d462e2d1 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -33,6 +33,7 @@ module.exports = { orange: '#FD8E28', violet: '#AD6CFF', 'gray-300': '#999999', + red: '#F43F4C', }, maxWidth: { screen: '100vw', diff --git a/frontend/translations/en.json b/frontend/translations/en.json index 4724ad6c..78efa932 100644 --- a/frontend/translations/en.json +++ b/frontend/translations/en.json @@ -98,7 +98,12 @@ "outro-button": "Get in touch", "looking-for": "I am looking for..." }, - "progress-tracker": {}, + "progress-tracker": { + "terrestrial-data-disclaimer-dialog-title": "Terrestrial Conservation Coverage Data Disclaimer", + "terrestrial-data-disclaimer-dialog-content": "Protected area totals and trends in this dataset are derived from publicly available boundaries sourced from Protected Planet. However, some countries restrict the release of detailed protected area boundaries, leading to differences between our totals and those reported by the World Database on Protected Areas.



Due to these data restrictions, our protected area totals are lower both globally and in regions where boundaries are not publicly accessible.", + "i-understand": "I understand", + "important-notification": "Important notification" + }, "conservation-builder": {} }, "containers": { @@ -230,7 +235,8 @@ "explore-terrestrial-conservation": "Explore Terrestrial Conservation", "explore-marine-conservation": "Explore Marine Conservation", "terrestrial-existing-conservation": "Existing terrestrial conservation coverage", - "not-assessed": "Not assessed" + "not-assessed": "Not assessed", + "data-disclaimer": "Data disclaimer" }, "map-sidebar-layers-panel": { "layers": "Layers", @@ -360,7 +366,8 @@ }, "tooltip-button": { "info": "Info", - "data-source": "Data source" + "data-source": "Data source", + "data-sources:": "Data sources:" }, "ui-carousel": { "previous-slide": "Previous slide",