From 48931c70da3edac177d5289a4f1f395d86d0c511 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Wed, 6 Mar 2024 12:28:16 +0000 Subject: [PATCH 001/134] Conservation chart to receive the actual data, and fill in bogus values to make space for the future projections --- .../charts/conservation-chart/index.tsx | 38 ++++++++++++++++--- .../widgets/marine-conservation/index.tsx | 28 ++------------ 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/frontend/src/components/charts/conservation-chart/index.tsx b/frontend/src/components/charts/conservation-chart/index.tsx index b61670ba..bedd21c7 100644 --- a/frontend/src/components/charts/conservation-chart/index.tsx +++ b/frontend/src/components/charts/conservation-chart/index.tsx @@ -33,10 +33,38 @@ type ConservationChartProps = { }[]; }; +const TARGET_YEAR = 2030; + const ConservationChart: React.FC = ({ className, data }) => { - const firstYearData = data[0]; - const lastYearData = data[data?.length - 1]; - const activeYearData = data.find(({ active }) => active); + const barChartData = useMemo(() => { + // Last year of data available + const lastEntryYear = data[data.length - 1]?.year; + + // Add bogus values from the last year to the target year (2030) to the array, so that the chart + // displays years from the beginning of the historical data, until the target year (projection). + const missingYearsArr = [...Array(TARGET_YEAR - lastEntryYear).keys()].map( + (i) => i + lastEntryYear + 1 + ); + + const missingYearsData = missingYearsArr.map((year) => { + return { + percentage: 0, + year: year, + active: false, + totalArea: null, + protectedArea: null, + future: true, + }; + }); + + // Cap results to the least 20 entries, or chart will be too big + return [...data, ...missingYearsData].slice(-20); + }, [data]); + + // Not using useMEmo as it may not be worth the overhead, performance wise + const firstYearData = barChartData[0]; + const lastYearData = barChartData[barChartData?.length - 1]; + const activeYearData = barChartData.find(({ active }) => active); const xAxisTicks = [firstYearData.year, activeYearData.year, lastYearData.year]; const historicalLineData = [ @@ -83,7 +111,7 @@ const ConservationChart: React.FC = ({ className, data } return (
- + = ({ className, data } dot={false} /> - {data.map((entry, index) => ( + {barChartData.map((entry, index) => ( = ({ loc const chartData = useMemo(() => { if (!mergedProtectionStats?.length) return []; - const totalArea = location.totalMarineArea; - const parsedData = mergedProtectionStats.map((entry, index) => { + const data = mergedProtectionStats.map((entry, index) => { const isLastYear = index === mergedProtectionStats.length - 1; const { year, protectedArea } = entry; - const percentage = Math.round((protectedArea * 100) / totalArea); + const percentage = Math.round((protectedArea * 100) / location.totalMarineArea); return { // We only want to show up to 55%, so we'll cap the percentage here @@ -109,32 +108,13 @@ const MarineConservationWidget: React.FC = ({ loc percentage: percentage > 55 ? 55 : percentage, year, active: isLastYear, - totalArea: totalArea, + totalArea: location.totalMarineArea, protectedArea, future: false, }; }); - const lastEntryYear = parsedData[parsedData.length - 1]?.year; - const missingYearsArr = [...Array(2030 - lastEntryYear).keys()].map( - (i) => i + lastEntryYear + 1 - ); - - const missingYearsData = missingYearsArr.map((year) => { - return { - percentage: 0, - year: year, - active: false, - totalArea: null, - protectedArea: null, - future: true, - }; - }); - - const mergedData = [...parsedData, ...missingYearsData]; - - // Cap results to the least 20 entries, or chart will be too big - return mergedData.slice(-20); + return data; }, [location, mergedProtectionStats]); const noData = !chartData.length; From 2c3eb4734c9a5e90b98f99896d0b5663c3b296f7 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Wed, 6 Mar 2024 12:57:37 +0000 Subject: [PATCH 002/134] Enable recharts tooltip for the ConservationChart --- frontend/src/components/charts/conservation-chart/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/charts/conservation-chart/index.tsx b/frontend/src/components/charts/conservation-chart/index.tsx index bedd21c7..55b8e196 100644 --- a/frontend/src/components/charts/conservation-chart/index.tsx +++ b/frontend/src/components/charts/conservation-chart/index.tsx @@ -8,7 +8,7 @@ import { CartesianGrid, ResponsiveContainer, Cell, - // Tooltip, + Tooltip, ReferenceLine, Line, } from 'recharts'; @@ -205,6 +205,7 @@ const ConservationChart: React.FC = ({ className, data } /> ))} + From 5ddc3bf2a5bf9b7eae9cdd64e0aa0b64b8085ed6 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Wed, 6 Mar 2024 12:59:50 +0000 Subject: [PATCH 003/134] Extrapolate ConservationChart historical line data in order for tooltips to work --- .../charts/conservation-chart/index.tsx | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/charts/conservation-chart/index.tsx b/frontend/src/components/charts/conservation-chart/index.tsx index 55b8e196..51d82b15 100644 --- a/frontend/src/components/charts/conservation-chart/index.tsx +++ b/frontend/src/components/charts/conservation-chart/index.tsx @@ -8,7 +8,7 @@ import { CartesianGrid, ResponsiveContainer, Cell, - Tooltip, + // Tooltip, ReferenceLine, Line, } from 'recharts'; @@ -34,6 +34,7 @@ type ConservationChartProps = { }; const TARGET_YEAR = 2030; +const LINE_LAST_COLUMN_OFFSET = 0.5; const ConservationChart: React.FC = ({ className, data }) => { const barChartData = useMemo(() => { @@ -61,16 +62,38 @@ const ConservationChart: React.FC = ({ className, data } return [...data, ...missingYearsData].slice(-20); }, [data]); - // Not using useMEmo as it may not be worth the overhead, performance wise + // Not using useMemo as it may not be worth the overhead, performance wise const firstYearData = barChartData[0]; const lastYearData = barChartData[barChartData?.length - 1]; const activeYearData = barChartData.find(({ active }) => active); const xAxisTicks = [firstYearData.year, activeYearData.year, lastYearData.year]; - const historicalLineData = [ - { year: firstYearData.year, percentage: firstYearData.percentage }, - { year: activeYearData.year + 0.5, percentage: activeYearData.percentage }, - ]; + // Calculate data for the historical line; first and active year are known, years in between + // need to be extrapolated. + const historicalLineData = useMemo(() => { + const missingYearsArr = [...Array(activeYearData.year - firstYearData.year - 1).keys()].map( + (i) => i + firstYearData.year + 1 + ); + + const delta = + (activeYearData.percentage - firstYearData.percentage) / (13 + LINE_LAST_COLUMN_OFFSET); + + const extrapolatedHistoricalYears = missingYearsArr.map((year, idx) => { + return { + year, + percentage: firstYearData.percentage + delta * (idx + 1), + }; + }); + + return [ + { year: firstYearData.year, percentage: firstYearData.percentage }, + ...extrapolatedHistoricalYears, + { + year: activeYearData.year + LINE_LAST_COLUMN_OFFSET, + percentage: activeYearData.percentage, + }, + ]; + }, [activeYearData, firstYearData]); const projectedPercentage = useMemo(() => { const numHistoricalYears = activeYearData.year - firstYearData.year; @@ -205,7 +228,8 @@ const ConservationChart: React.FC = ({ className, data } /> ))} - + + {/* */} From fd4f3fc97455892d07038e7c256460c3d424deb2 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Thu, 7 Mar 2024 12:00:16 +0000 Subject: [PATCH 004/134] Align 30x30 target tooltip button in the coverage chart --- frontend/src/components/charts/conservation-chart/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/charts/conservation-chart/index.tsx b/frontend/src/components/charts/conservation-chart/index.tsx index 51d82b15..998e8d6b 100644 --- a/frontend/src/components/charts/conservation-chart/index.tsx +++ b/frontend/src/components/charts/conservation-chart/index.tsx @@ -155,7 +155,7 @@ const ConservationChart: React.FC = ({ className, data } > From e4021c10cd032506f11d6d964852a13f5b5d8869 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Wed, 6 Mar 2024 14:14:01 +0000 Subject: [PATCH 005/134] Redo ConservationChart calculations and pass data correctly to recharts components --- .../charts/conservation-chart/index.tsx | 111 +++++++++++------- .../conservation-chart/tooltip/index.tsx | 57 +++++---- 2 files changed, 99 insertions(+), 69 deletions(-) diff --git a/frontend/src/components/charts/conservation-chart/index.tsx b/frontend/src/components/charts/conservation-chart/index.tsx index 998e8d6b..bcadff9e 100644 --- a/frontend/src/components/charts/conservation-chart/index.tsx +++ b/frontend/src/components/charts/conservation-chart/index.tsx @@ -8,7 +8,7 @@ import { CartesianGrid, ResponsiveContainer, Cell, - // Tooltip, + Tooltip, ReferenceLine, Line, } from 'recharts'; @@ -18,7 +18,6 @@ import { cn } from '@/lib/classnames'; import { useGetDataInfos } from '@/types/generated/data-info'; import ChartLegend from './legend'; - // import ChartTooltip from './tooltip'; type ConservationChartProps = { @@ -34,7 +33,7 @@ type ConservationChartProps = { }; const TARGET_YEAR = 2030; -const LINE_LAST_COLUMN_OFFSET = 0.5; +const MAX_NUM_YEARS = 20; const ConservationChart: React.FC = ({ className, data }) => { const barChartData = useMemo(() => { @@ -49,7 +48,7 @@ const ConservationChart: React.FC = ({ className, data } const missingYearsData = missingYearsArr.map((year) => { return { - percentage: 0, + percentage: null, year: year, active: false, totalArea: null, @@ -67,6 +66,9 @@ const ConservationChart: React.FC = ({ className, data } const lastYearData = barChartData[barChartData?.length - 1]; const activeYearData = barChartData.find(({ active }) => active); const xAxisTicks = [firstYearData.year, activeYearData.year, lastYearData.year]; + const numHistoricalYears = activeYearData?.year - firstYearData?.year; + const historicalDelta = + (activeYearData.percentage - firstYearData.percentage) / numHistoricalYears; // Calculate data for the historical line; first and active year are known, years in between // need to be extrapolated. @@ -75,13 +77,10 @@ const ConservationChart: React.FC = ({ className, data } (i) => i + firstYearData.year + 1 ); - const delta = - (activeYearData.percentage - firstYearData.percentage) / (13 + LINE_LAST_COLUMN_OFFSET); - const extrapolatedHistoricalYears = missingYearsArr.map((year, idx) => { return { year, - percentage: firstYearData.percentage + delta * (idx + 1), + percentage: firstYearData.percentage + historicalDelta * (idx + 1), }; }); @@ -89,33 +88,31 @@ const ConservationChart: React.FC = ({ className, data } { year: firstYearData.year, percentage: firstYearData.percentage }, ...extrapolatedHistoricalYears, { - year: activeYearData.year + LINE_LAST_COLUMN_OFFSET, + year: activeYearData.year, percentage: activeYearData.percentage, }, ]; - }, [activeYearData, firstYearData]); - - const projectedPercentage = useMemo(() => { - const numHistoricalYears = activeYearData.year - firstYearData.year; - const numProjectedYears = lastYearData.year - activeYearData.year; - - const projectedPercentageChange = - ((activeYearData.percentage - firstYearData.percentage) / numHistoricalYears) * - numProjectedYears; - - return activeYearData.percentage + projectedPercentageChange; - }, [ - activeYearData.percentage, - activeYearData.year, - firstYearData.percentage, - firstYearData.year, - lastYearData.year, - ]); - - const projectedLineData = [ - { year: activeYearData.year + 0.5, percentage: activeYearData.percentage }, - { year: lastYearData.year, percentage: projectedPercentage }, - ]; + }, [activeYearData, firstYearData, historicalDelta]); + + // Calculate data for the projected line; we know the active and target years; extrapolate + // the projection based on the historical data. + const projectedLineData = useMemo(() => { + const yearsArray = [...Array(TARGET_YEAR - activeYearData.year).keys()].map( + (i) => i + activeYearData.year + 1 + ); + + const extrapolatedProjectedYears = yearsArray.map((year, idx) => { + return { + year, + percentage: activeYearData.percentage + historicalDelta * (idx + 1), + }; + }); + + return [ + { year: activeYearData.year, percentage: activeYearData.percentage }, + ...extrapolatedProjectedYears, + ]; + }, [activeYearData, historicalDelta]); const { data: dataInfo } = useGetDataInfos( { @@ -131,10 +128,39 @@ const ConservationChart: React.FC = ({ className, data } } ); + const chartData = useMemo(() => { + const historicalYearsArray = data?.map(({ year }) => year); + const lastDataYear = historicalYearsArray[historicalYearsArray.length - 1]; + const futureYearsArray = [...Array(TARGET_YEAR - lastDataYear).keys()].map( + (i) => i + lastDataYear + 1 + ); + const allYearsArray = [...historicalYearsArray, ...futureYearsArray]; + + return allYearsArray + .map((year) => { + const percentage = data?.find(({ year: dataYear }) => year === dataYear)?.percentage; + const historical = historicalLineData?.find( + ({ year: historicalYear }) => year === historicalYear + )?.percentage; + const projected = projectedLineData?.find( + ({ year: projectedYear }) => year === projectedYear + )?.percentage; + + return { + year, + percentage, + historical, + projected, + active: year === lastDataYear, + }; + }) + ?.slice(-MAX_NUM_YEARS); + }, [data, historicalLineData, projectedLineData]); + return (
- + = ({ className, data } ticks={[0, 15, 30, 45, 55]} tickFormatter={(value) => `${value}%`} /> - {/* - // TODO: Investigate tooltip - // Tooltip does not play nice when the Line charts are used (no payload) - } /> - */} - {barChartData.map((entry, index) => ( + {chartData.map((entry, index) => ( = ({ className, data } /> ))} - - {/* */} + {/* */} + diff --git a/frontend/src/components/charts/conservation-chart/tooltip/index.tsx b/frontend/src/components/charts/conservation-chart/tooltip/index.tsx index 736f7cc7..41a32889 100644 --- a/frontend/src/components/charts/conservation-chart/tooltip/index.tsx +++ b/frontend/src/components/charts/conservation-chart/tooltip/index.tsx @@ -1,35 +1,44 @@ -import React, { useMemo } from 'react'; +import React /*, { useMemo }*/ from 'react'; -import { format } from 'd3-format'; +// import { format } from 'd3-format'; const ChartTooltip = ({ active, payload }) => { - const { percentage, year, protectedArea, totalArea, future } = payload[0]?.payload || {}; + if (!active || !payload?.[2]) return null; - const formattedAreas = useMemo(() => { - return { - protected: format(',.2r')(protectedArea), - total: format(',.2r')(totalArea), - }; - }, [protectedArea, totalArea]); - - if (!active || !payload?.length) return null; + // console.log({ active, payload }); return ( -
- Year: {year} - {!future && ( - <> - Protection percentage: {percentage}% - - Protected area: {formattedAreas.protected} km2 - - - Total area: {formattedAreas.total} km2 - - - )} +
+ Coverage: {payload?.[2]?.value}%
); + // const { percentage, year, protectedArea, totalArea, future } = payload?.[0]?.payload || {}; + + // const formattedAreas = useMemo(() => { + // return { + // protected: format(',.2r')(protectedArea), + // total: format(',.2r')(totalArea), + // }; + // }, [protectedArea, totalArea]); + + // if (!active || !payload?.length) return null; + + // return ( + //
+ // Year: {year} + // {!future && ( + // <> + // Protection percentage: {percentage}% + // + // Protected area: {formattedAreas.protected} km2 + // + // + // Total area: {formattedAreas.total} km2 + // + // + // )} + //
+ // ); }; export default ChartTooltip; From e474f96659318ee4ed07d31651445bdc8e53e5e4 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Thu, 7 Mar 2024 11:50:22 +0000 Subject: [PATCH 006/134] Add tooltip to the bar chart in the Marine Conservation Coverage widget chart --- .../charts/conservation-chart/index.tsx | 5 +-- .../conservation-chart/tooltip/index.tsx | 41 ++++--------------- 2 files changed, 11 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/charts/conservation-chart/index.tsx b/frontend/src/components/charts/conservation-chart/index.tsx index bcadff9e..de166cc5 100644 --- a/frontend/src/components/charts/conservation-chart/index.tsx +++ b/frontend/src/components/charts/conservation-chart/index.tsx @@ -18,7 +18,7 @@ import { cn } from '@/lib/classnames'; import { useGetDataInfos } from '@/types/generated/data-info'; import ChartLegend from './legend'; -// import ChartTooltip from './tooltip'; +import ChartTooltip from './tooltip'; type ConservationChartProps = { className?: string; @@ -249,8 +249,7 @@ const ConservationChart: React.FC = ({ className, data } /> ))} - {/* */} - + diff --git a/frontend/src/components/charts/conservation-chart/tooltip/index.tsx b/frontend/src/components/charts/conservation-chart/tooltip/index.tsx index 41a32889..027ff4e4 100644 --- a/frontend/src/components/charts/conservation-chart/tooltip/index.tsx +++ b/frontend/src/components/charts/conservation-chart/tooltip/index.tsx @@ -1,44 +1,21 @@ -import React /*, { useMemo }*/ from 'react'; +import React from 'react'; -// import { format } from 'd3-format'; +import { formatPercentage } from '@/lib/utils/formats'; const ChartTooltip = ({ active, payload }) => { - if (!active || !payload?.[2]) return null; + if (!active || !payload) return null; - // console.log({ active, payload }); + const percentageData = payload?.find(({ dataKey }) => dataKey === 'percentage'); + if (!percentageData?.payload) return null; + + const { percentage, year } = percentageData?.payload; return (
- Coverage: {payload?.[2]?.value}% + Year: {year} + Coverage: {formatPercentage(percentage)}
); - // const { percentage, year, protectedArea, totalArea, future } = payload?.[0]?.payload || {}; - - // const formattedAreas = useMemo(() => { - // return { - // protected: format(',.2r')(protectedArea), - // total: format(',.2r')(totalArea), - // }; - // }, [protectedArea, totalArea]); - - // if (!active || !payload?.length) return null; - - // return ( - //
- // Year: {year} - // {!future && ( - // <> - // Protection percentage: {percentage}% - // - // Protected area: {formattedAreas.protected} km2 - // - // - // Total area: {formattedAreas.total} km2 - // - // - // )} - //
- // ); }; export default ChartTooltip; From 452008437df80201db235246b68ad9ca5e2420e1 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Thu, 7 Mar 2024 12:24:55 +0000 Subject: [PATCH 007/134] Fix linting issues in _app.tsx --- frontend/src/pages/_app.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index 6b7ebf91..4da5ce4e 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -9,8 +9,8 @@ import { QueryClient, QueryClientProvider, Hydrate } from '@tanstack/react-query import 'styles/globals.css'; import 'mapbox-gl/dist/mapbox-gl.css'; -import { figtree, overpassMono } from '@/styles/fonts'; import Analytics from '@/components/analytics'; +import { figtree, overpassMono } from '@/styles/fonts'; type PageProps = { dehydratedState: unknown; From 72624e26a1f576078daa74f2b6442cdcddb3ac6e Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Thu, 7 Mar 2024 12:34:05 +0000 Subject: [PATCH 008/134] Do not round coverage percentage for the marine conservation coverage widget chart --- .../map/sidebar/details/widgets/marine-conservation/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/containers/map/sidebar/details/widgets/marine-conservation/index.tsx b/frontend/src/containers/map/sidebar/details/widgets/marine-conservation/index.tsx index d74df268..4db4d3c4 100644 --- a/frontend/src/containers/map/sidebar/details/widgets/marine-conservation/index.tsx +++ b/frontend/src/containers/map/sidebar/details/widgets/marine-conservation/index.tsx @@ -100,7 +100,7 @@ const MarineConservationWidget: React.FC = ({ loc const data = mergedProtectionStats.map((entry, index) => { const isLastYear = index === mergedProtectionStats.length - 1; const { year, protectedArea } = entry; - const percentage = Math.round((protectedArea * 100) / location.totalMarineArea); + const percentage = (protectedArea * 100) / location.totalMarineArea; return { // We only want to show up to 55%, so we'll cap the percentage here From 6719c949f1830279f37b13ac4044a855447a437a Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Mon, 11 Mar 2024 12:09:52 +0000 Subject: [PATCH 009/134] Details tables area columns to use the same area formatter as the rest of the app for consistency purposes --- .../src/containers/map/content/details/table/helpers.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/containers/map/content/details/table/helpers.ts b/frontend/src/containers/map/content/details/table/helpers.ts index 59a69d66..17639d69 100644 --- a/frontend/src/containers/map/content/details/table/helpers.ts +++ b/frontend/src/containers/map/content/details/table/helpers.ts @@ -1,13 +1,11 @@ -import { format } from 'd3-format'; - -import { formatPercentage } from '@/lib/utils/formats'; +import { formatPercentage, formatKM } from '@/lib/utils/formats'; const percentage = (value: number) => { return formatPercentage(value, { displayPercentageSign: false }); }; const area = (value: number) => { - return format(',.2r')(value); + return formatKM(value); }; const capitalize = (value: string) => { From fa1edb71286c42efc26a72804bc91b3e9509e99e Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Mon, 11 Mar 2024 13:02:23 +0000 Subject: [PATCH 010/134] Revert "Ensure that in the global/regional table, area does not exceed the total marine area due to rounding" This reverts commit 4ba6019c000ecf286ea73083d1c4dc18aa2fa903. No longer needed --- .../details/tables/global-regional/index.tsx | 1 - .../details/tables/global-regional/useColumns.tsx | 14 ++------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/frontend/src/containers/map/content/details/tables/global-regional/index.tsx b/frontend/src/containers/map/content/details/tables/global-regional/index.tsx index 0c63dbf4..5ae7bd1c 100644 --- a/frontend/src/containers/map/content/details/tables/global-regional/index.tsx +++ b/frontend/src/containers/map/content/details/tables/global-regional/index.tsx @@ -168,7 +168,6 @@ const GlobalRegionalTable: React.FC = () => { return { location: location.name, locationCode: location.code, - totalMarineArea: location.totalMarineArea, coverage: coveragePercentage, area: protectedArea, locationType: location.type, diff --git a/frontend/src/containers/map/content/details/tables/global-regional/useColumns.tsx b/frontend/src/containers/map/content/details/tables/global-regional/useColumns.tsx index ea366ded..61a69ead 100644 --- a/frontend/src/containers/map/content/details/tables/global-regional/useColumns.tsx +++ b/frontend/src/containers/map/content/details/tables/global-regional/useColumns.tsx @@ -15,7 +15,6 @@ import { useMapSearchParams } from '@/containers/map/content/map/sync-settings'; export type GlobalRegionalTableColumns = { location: string; locationCode: string; - totalMarineArea: number; coverage: number; locationType: string; mpas: number; @@ -86,20 +85,11 @@ const useColumns = () => { ), cell: ({ row }) => { - const { totalMarineArea, area: value } = row.original; + const { area: value } = row.original; const formattedValue = cellFormatter.area(value); - let displayValue = formattedValue; - - // In certain circumstances, due to rounding, the formatted value may exceed the - // total marine area, mostly in situations where the coverage is nearing 100%. - // In this case, we skip the rounding and display the value. - if (parseFloat(formattedValue) > totalMarineArea) { - displayValue = value.toString(); - } - return ( - {displayValue} km2 + {formattedValue} km2 ); }, From 9212c90750b4df3403e08a2c1d240efb54192bff Mon Sep 17 00:00:00 2001 From: Agnieszka Figiel Date: Mon, 11 Mar 2024 20:43:33 +0100 Subject: [PATCH 011/134] Set the new staging subdomain --- infrastructure/base/vars/terraform.tfvars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infrastructure/base/vars/terraform.tfvars b/infrastructure/base/vars/terraform.tfvars index 9ec32da0..406503c6 100644 --- a/infrastructure/base/vars/terraform.tfvars +++ b/infrastructure/base/vars/terraform.tfvars @@ -9,7 +9,7 @@ staging_project_name = "x30-dev" production_project_name = "x30-prod" domain = "skytruth.org" -staging_subdomain = "30x30" # TMP +staging_subdomain = "30x30-dev" production_subdomain = "30x30" uptime_alert_email = "agnieszka.figiel@vizzuality.com" From ee779864b5df884714cedfe94c50203e270c89f1 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Wed, 13 Mar 2024 10:42:42 +0000 Subject: [PATCH 012/134] Horizontal bar chart to use same area formatter as the rest of the app --- .../src/components/charts/horizontal-bar-chart/index.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/charts/horizontal-bar-chart/index.tsx b/frontend/src/components/charts/horizontal-bar-chart/index.tsx index c5187cec..dc5527de 100644 --- a/frontend/src/components/charts/horizontal-bar-chart/index.tsx +++ b/frontend/src/components/charts/horizontal-bar-chart/index.tsx @@ -1,10 +1,8 @@ import { useMemo } from 'react'; -import { format } from 'd3-format'; - import TooltipButton from '@/components/tooltip-button'; import { cn } from '@/lib/classnames'; -import { formatPercentage } from '@/lib/utils/formats'; +import { formatPercentage, formatKM } from '@/lib/utils/formats'; const DEFAULT_MAX_PERCENTAGE = 100; const PROTECTION_TARGET = 30; @@ -47,7 +45,7 @@ const HorizontalBarChart: React.FC = ({ }, [protectedArea, totalArea]); const formattedArea = useMemo(() => { - return format(',.0f')(totalArea); + return formatKM(totalArea); }, [totalArea]); return ( From b1cc8ab08a9ffa4833620b521c6a0309207619c8 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Wed, 13 Mar 2024 10:43:21 +0000 Subject: [PATCH 013/134] Remove d3-format - no longer in use --- frontend/package.json | 1 - frontend/yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 05353847..898ec73e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,7 +50,6 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", - "d3-format": "^3.1.0", "d3-time-format": "^4.1.0", "date-fns": "^2.30.0", "deck.gl": "8.9.31", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index ea8e76d6..3820e8a9 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -11845,7 +11845,6 @@ __metadata: class-variance-authority: ^0.7.0 clsx: ^2.0.0 cmdk: ^0.2.0 - d3-format: ^3.1.0 d3-time-format: ^4.1.0 date-fns: ^2.30.0 deck.gl: 8.9.31 From cbe204c4fad042a3f6cfb4719d2eaf4d022deb3d Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Wed, 13 Mar 2024 10:48:13 +0000 Subject: [PATCH 014/134] Area formatter to display '<1' when area is smaller than 1 but bigger than 0 --- frontend/src/lib/utils/formats.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/lib/utils/formats.ts b/frontend/src/lib/utils/formats.ts index 27227bed..fa0f3ea5 100644 --- a/frontend/src/lib/utils/formats.ts +++ b/frontend/src/lib/utils/formats.ts @@ -24,6 +24,8 @@ export function formatPercentage( } export function formatKM(value: number, options?: Intl.NumberFormatOptions) { + if (value < 1 && value > 0) return '<1'; + const v = Intl.NumberFormat('en-US', { notation: 'standard', compactDisplay: 'short', From 2194f9c2c60d2a804954c71e25fa250a2a1f27c6 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Wed, 13 Mar 2024 11:05:35 +0000 Subject: [PATCH 015/134] Fix incorrect external URL in the About page --- frontend/src/pages/about.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/about.tsx b/frontend/src/pages/about.tsx index 3d6021fe..5fe21050 100644 --- a/frontend/src/pages/about.tsx +++ b/frontend/src/pages/about.tsx @@ -122,7 +122,7 @@ const About: React.FC = ({ . This target has been{' '} From e0af3be619de428745068e594a0134ec92d7cfdb Mon Sep 17 00:00:00 2001 From: Agnieszka Figiel Date: Thu, 21 Mar 2024 12:44:39 +0100 Subject: [PATCH 016/134] Infrastructure documentation update --- infrastructure/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/infrastructure/README.md b/infrastructure/README.md index e8bdae4d..44174a2b 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -68,6 +68,11 @@ Please note, there are some actions that might to be carried out manually - you' First, authenticate with GCP. The easiest way to do this is to run `gcloud auth application-default login`. +To verify your credentials were applied correctly, you can use +`gcloud projects list` +to check projects available to the authenticated account. To make double sure you're working on the correct project, you can also run +`gcloud config set project [project id]` + Next, check and adapt if necessary the contents of `infrastructure/base/vars/terraform.tfvars`. For example, set the email address used for sendig alert notifications. You will need to initialize both terraform projects by running: @@ -85,7 +90,7 @@ While in `infrastructure/remote-state` directory: 2. Apply the `Base` project. This needs to be repeated after any changes in the base project. -_Please note: when doing this for the first time in a clean project, temporarily change the `cloudrun` module (`infrastructure/base/modules/cloudrun/main.tf`) by uncommenting the default image setting, so that it deploys a dummy "hello" image. This is to work around the fact that actual application images are going to be available in the image repository only once the infrastructure is provisioned and the GH Actions deployment passed._ +_Please note: when doing this for the first time in a clean project, or a new environment, use the `use_hello_world_image` variable, so that it deploys a dummy "hello" image. This is to work around the fact that actual application images are going to be available in the image repository only once the infrastructure is provisioned and the GH Actions deployment passed._ While in `infrastructure/base` directory: From d76445acac76b6c2509e1955c008b1743d106fe5 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Wed, 27 Mar 2024 09:42:53 +0000 Subject: [PATCH 017/134] Add support for basic Auth --- frontend/package.json | 1 + frontend/src/middleware.ts | 17 +++ frontend/src/pages/api/auth/[...nextauth].ts | 36 ++++++ frontend/yarn.lock | 123 +++++++++++++++++++ 4 files changed, 177 insertions(+) create mode 100644 frontend/src/middleware.ts create mode 100644 frontend/src/pages/api/auth/[...nextauth].ts diff --git a/frontend/package.json b/frontend/package.json index 898ec73e..037fb2e6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -58,6 +58,7 @@ "lucide-react": "^0.274.0", "mapbox-gl": "3.1.2", "next": "13.5.6", + "next-auth": "^4.24.7", "next-usequerystate": "1.9.2", "orval": "6.18.1", "postcss": "8.4.21", diff --git a/frontend/src/middleware.ts b/frontend/src/middleware.ts new file mode 100644 index 00000000..f778ab85 --- /dev/null +++ b/frontend/src/middleware.ts @@ -0,0 +1,17 @@ +import { NextRequest, NextResponse } from 'next/server'; + +import nextAuthMiddleware, { NextRequestWithAuth } from 'next-auth/middleware'; + +export function middleware(params: NextRequest) { + const authRequired = + !!process.env.HTTP_AUTH_USERNAME && + !!process.env.HTTP_AUTH_PASSWORD && + !!process.env.NEXTAUTH_SECRET && + !!process.env.NEXTAUTH_URL; + + if (authRequired) { + return nextAuthMiddleware(params as NextRequestWithAuth); + } + + return NextResponse.next(); +} diff --git a/frontend/src/pages/api/auth/[...nextauth].ts b/frontend/src/pages/api/auth/[...nextauth].ts new file mode 100644 index 00000000..844baabb --- /dev/null +++ b/frontend/src/pages/api/auth/[...nextauth].ts @@ -0,0 +1,36 @@ +import NextAuth from 'next-auth'; +import CredentialsProvider from 'next-auth/providers/credentials'; + +export const authOptions = { + providers: [ + CredentialsProvider({ + name: 'credentials', + credentials: { + username: { label: 'Username', type: 'text' }, + password: { label: 'Password', type: 'password' }, + }, + async authorize(credentials) { + /** Return `true` if the user and password are correct */ + const matchCredentials = (username: string, password: string) => { + let valid = true; + + valid = username === process.env.HTTP_AUTH_USERNAME && valid; + valid = password === process.env.HTTP_AUTH_PASSWORD && valid; + + return valid; + }; + + const validCredentials = + !!credentials && matchCredentials(credentials.username, credentials.password); + + if (validCredentials) { + return { id: 'shared-user' }; + } + + return null; + }, + }), + ], +}; + +export default NextAuth(authOptions); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 3820e8a9..cebd7e77 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -72,6 +72,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.20.13": + version: 7.24.1 + resolution: "@babel/runtime@npm:7.24.1" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 5c8f3b912ba949865f03b3cf8395c60e1f4ebd1033fbd835bdfe81b6cac8a87d85bc3c7aded5fcdf07be044c9ab8c818f467abe0deca50020c72496782639572 + languageName: node + linkType: hard + "@deck.gl/aggregation-layers@npm:8.9.31": version: 8.9.31 resolution: "@deck.gl/aggregation-layers@npm:8.9.31" @@ -1259,6 +1268,13 @@ __metadata: languageName: node linkType: hard +"@panva/hkdf@npm:^1.0.2": + version: 1.1.1 + resolution: "@panva/hkdf@npm:1.1.1" + checksum: f0dd12903751d8792420353f809ed3c7de860cf506399759fff5f59f7acfef8a77e2b64012898cee7e5b047708fa0bd91dff5ef55a502bf8ea11aad9842160da + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -5778,6 +5794,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:^0.5.0": + version: 0.5.0 + resolution: "cookie@npm:0.5.0" + checksum: 1f4bd2ca5765f8c9689a7e8954183f5332139eb72b6ff783d8947032ec1fdf43109852c178e21a953a30c0dd42257828185be01b49d1eb1a67fd054ca588a180 + languageName: node + linkType: hard + "copy-descriptor@npm:^0.1.0": version: 0.1.1 resolution: "copy-descriptor@npm:0.1.1" @@ -8906,6 +8929,13 @@ __metadata: languageName: node linkType: hard +"jose@npm:^4.15.5": + version: 4.15.5 + resolution: "jose@npm:4.15.5" + checksum: 7dde76447c7707bd4b448f914b216f3858e701aa83f00447434252461af5b9e159dcbffb88badea3f9616739526763581267c9560622f0a058df8d68c86d7f79 + languageName: node + linkType: hard + "jotai@npm:2.4.3": version: 2.4.3 resolution: "jotai@npm:2.4.3" @@ -9913,6 +9943,31 @@ __metadata: languageName: node linkType: hard +"next-auth@npm:^4.24.7": + version: 4.24.7 + resolution: "next-auth@npm:4.24.7" + dependencies: + "@babel/runtime": ^7.20.13 + "@panva/hkdf": ^1.0.2 + cookie: ^0.5.0 + jose: ^4.15.5 + oauth: ^0.9.15 + openid-client: ^5.4.0 + preact: ^10.6.3 + preact-render-to-string: ^5.1.19 + uuid: ^8.3.2 + peerDependencies: + next: ^12.2.5 || ^13 || ^14 + nodemailer: ^6.6.5 + react: ^17.0.2 || ^18 + react-dom: ^17.0.2 || ^18 + peerDependenciesMeta: + nodemailer: + optional: true + checksum: e7849ecf86394d86f08730b96f869c9353b6cc003794fcd953db2949f1ab06a2ce4f7a67312fdb8c7b3bbe7e99a8a215f11ef4eba5ec93f6c3e8ac1954e8b3e6 + languageName: node + linkType: hard + "next-usequerystate@npm:1.9.2": version: 1.9.2 resolution: "next-usequerystate@npm:1.9.2" @@ -10181,6 +10236,13 @@ __metadata: languageName: node linkType: hard +"oauth@npm:^0.9.15": + version: 0.9.15 + resolution: "oauth@npm:0.9.15" + checksum: 957c0d8d85300398dcb0e293953650c0fc3facc795bee8228238414f19f59cef5fd4ee8d17a972c142924c10c5f6ec50ef80f77f4a6cc6e3c98f9d22c027801c + languageName: node + linkType: hard + "object-assign@npm:*, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -10199,6 +10261,13 @@ __metadata: languageName: node linkType: hard +"object-hash@npm:^2.2.0": + version: 2.2.0 + resolution: "object-hash@npm:2.2.0" + checksum: 55ba841e3adce9c4f1b9b46b41983eda40f854e0d01af2802d3ae18a7085a17168d6b81731d43fdf1d6bcbb3c9f9c56d22c8fea992203ad90a38d7d919bc28f1 + languageName: node + linkType: hard + "object-hash@npm:^3.0.0": version: 3.0.0 resolution: "object-hash@npm:3.0.0" @@ -10315,6 +10384,13 @@ __metadata: languageName: node linkType: hard +"oidc-token-hash@npm:^5.0.3": + version: 5.0.3 + resolution: "oidc-token-hash@npm:5.0.3" + checksum: 35fa19aea9ff2c509029ec569d74b778c8a215b92bd5e6e9bc4ebbd7ab035f44304ff02430a6397c3fb7c1d15ebfa467807ca0bcd31d06ba610b47798287d303 + languageName: node + linkType: hard + "once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -10351,6 +10427,18 @@ __metadata: languageName: node linkType: hard +"openid-client@npm:^5.4.0": + version: 5.6.5 + resolution: "openid-client@npm:5.6.5" + dependencies: + jose: ^4.15.5 + lru-cache: ^6.0.0 + object-hash: ^2.2.0 + oidc-token-hash: ^5.0.3 + checksum: 2240079f761173b10635ce5fefbac04b6820f54e00d588ab2afdddb6c0f0ab6568e663cf1ab6a4a2297fbdbb73e42d78b8190f91dba7e1b80d287b2127fcbc7c + languageName: node + linkType: hard + "optionator@npm:^0.9.1": version: 0.9.3 resolution: "optionator@npm:0.9.3" @@ -10795,6 +10883,24 @@ __metadata: languageName: node linkType: hard +"preact-render-to-string@npm:^5.1.19": + version: 5.2.6 + resolution: "preact-render-to-string@npm:5.2.6" + dependencies: + pretty-format: ^3.8.0 + peerDependencies: + preact: ">=10" + checksum: be8d5d8fb502d422c503e68af7bcccb6facd942f3ae9a4d093ebe3f1d4f0b15c540624bdac434d53a2a8e8fb7afa4606383414e937c40933ca43445470a026ff + languageName: node + linkType: hard + +"preact@npm:^10.6.3": + version: 10.20.1 + resolution: "preact@npm:10.20.1" + checksum: af5ed9bdf44bfa5487479c09fa971a32902987f277c74d6244f9d9466ccd5c1efd3a0949e7dc2227177623878b1adafcc5c50cee6617a84f72d1cebe55ff76ba + languageName: node + linkType: hard + "prelude-ls@npm:^1.2.1": version: 1.2.1 resolution: "prelude-ls@npm:1.2.1" @@ -10829,6 +10935,13 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^3.8.0": + version: 3.8.0 + resolution: "pretty-format@npm:3.8.0" + checksum: 21a114d43ef06978f8f7f6212be4649b0b094f05d9b30e14e37550bf35c8ca24d8adbca9e5adc4cc15d9eaf7a1e7a30478a4dc37b30982bfdf0292a5b385484c + languageName: node + linkType: hard + "printable-characters@npm:^1.0.42": version: 1.0.42 resolution: "printable-characters@npm:1.0.42" @@ -11858,6 +11971,7 @@ __metadata: lucide-react: ^0.274.0 mapbox-gl: 3.1.2 next: 13.5.6 + next-auth: ^4.24.7 next-usequerystate: 1.9.2 orval: 6.18.1 postcss: 8.4.21 @@ -13023,6 +13137,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df + languageName: node + linkType: hard + "validate-npm-package-name@npm:3.0.0": version: 3.0.0 resolution: "validate-npm-package-name@npm:3.0.0" From e6d627468daf8a7985dfbff48bbbab973a9adf79 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Mon, 25 Mar 2024 12:53:41 +0000 Subject: [PATCH 018/134] Split Navigation "Map" link into "Progress Tracker" and "Conservation Builder" --- frontend/src/components/header.tsx | 3 ++- frontend/src/constants/pages.ts | 3 ++- frontend/src/containers/homepage/link-cards/index.tsx | 4 ++-- .../content/details/tables/global-regional/useColumns.tsx | 2 +- frontend/src/containers/map/content/map/popup/eez/index.tsx | 6 +++++- .../src/containers/map/content/map/popup/regions/index.tsx | 6 +++++- .../map/sidebar/details/location-selector/index.tsx | 4 +++- 7 files changed, 20 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx index fad4396e..f80d26c9 100644 --- a/frontend/src/components/header.tsx +++ b/frontend/src/components/header.tsx @@ -19,7 +19,8 @@ import { cn } from '@/lib/classnames'; import ArrowRight from '@/styles/icons/arrow-right.svg?sprite'; const NAVIGATION_ITEMS = [ - { name: 'Map', href: PAGES.map, colorClassName: 'text-blue' }, + { name: 'Progress Tracker', href: PAGES.progressTracker, colorClassName: 'text-orange' }, + { name: 'Conservation Builder', href: PAGES.conservationBuilder, colorClassName: 'text-blue' }, { name: 'Knowledge Hub', href: PAGES.knowledgeHub, colorClassName: 'text-green' }, { name: 'About', href: PAGES.about, colorClassName: 'text-violet' }, { name: 'Contact', href: PAGES.contact, colorClassName: 'text-black' }, diff --git a/frontend/src/constants/pages.ts b/frontend/src/constants/pages.ts index 0b29e00f..9c912f15 100644 --- a/frontend/src/constants/pages.ts +++ b/frontend/src/constants/pages.ts @@ -1,6 +1,7 @@ export const PAGES = { homepage: '/', - map: '/map', + progressTracker: '/progress-tracker', + conservationBuilder: '/conservation-builder', knowledgeHub: '/knowledge-hub', contact: '/contact', about: '/about', diff --git a/frontend/src/containers/homepage/link-cards/index.tsx b/frontend/src/containers/homepage/link-cards/index.tsx index c6db126a..47923c59 100644 --- a/frontend/src/containers/homepage/link-cards/index.tsx +++ b/frontend/src/containers/homepage/link-cards/index.tsx @@ -4,11 +4,11 @@ import LinkCard from '@/containers/homepage/link-cards/link-card'; const LinkCards: React.FC = () => (
{ {location} diff --git a/frontend/src/containers/map/content/map/popup/eez/index.tsx b/frontend/src/containers/map/content/map/popup/eez/index.tsx index 3895c943..a2d9e63b 100644 --- a/frontend/src/containers/map/content/map/popup/eez/index.tsx +++ b/frontend/src/containers/map/content/map/popup/eez/index.tsx @@ -156,7 +156,11 @@ const EEZLayerPopup = ({ locationId }) => { }, [map]); const handleLocationSelected = useCallback(async () => { - await push(`${PAGES.map}/${locationsQuery.data.code.toUpperCase()}?${searchParams.toString()}`); + await push( + `${ + PAGES.progressTracker + }/${locationsQuery.data.code.toUpperCase()}?${searchParams.toString()}` + ); setLocationBBox(locationsQuery.data.bounds); setPopup({}); }, [push, searchParams, setLocationBBox, locationsQuery.data, setPopup]); diff --git a/frontend/src/containers/map/content/map/popup/regions/index.tsx b/frontend/src/containers/map/content/map/popup/regions/index.tsx index e6079a80..09fc2133 100644 --- a/frontend/src/containers/map/content/map/popup/regions/index.tsx +++ b/frontend/src/containers/map/content/map/popup/regions/index.tsx @@ -163,7 +163,11 @@ const EEZLayerPopup = ({ locationId }) => { }, [map]); const handleLocationSelected = useCallback(async () => { - await push(`${PAGES.map}/${locationsQuery.data.code.toUpperCase()}?${searchParams.toString()}`); + await push( + `${ + PAGES.progressTracker + }/${locationsQuery.data.code.toUpperCase()}?${searchParams.toString()}` + ); setLocationBBox(locationsQuery.data.bounds); setPopup({}); }, [push, searchParams, setLocationBBox, locationsQuery.data, setPopup]); diff --git a/frontend/src/containers/map/sidebar/details/location-selector/index.tsx b/frontend/src/containers/map/sidebar/details/location-selector/index.tsx index 8e0956b8..65f1a7e1 100644 --- a/frontend/src/containers/map/sidebar/details/location-selector/index.tsx +++ b/frontend/src/containers/map/sidebar/details/location-selector/index.tsx @@ -79,7 +79,9 @@ const LocationSelector: React.FC = ({ className }) => { if (selectedLocation) { setLocationBBox(selectedLocation?.attributes.bounds); - await push(`${PAGES.map}/${locationCode.toUpperCase()}?${searchParams.toString()}`); + await push( + `${PAGES.progressTracker}/${locationCode.toUpperCase()}?${searchParams.toString()}` + ); } }, [push, searchParams, setLocationBBox, locationsData, setPopup] From e0fe3121279eb78b9ac4324b14d82090eaae4214 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Mon, 25 Mar 2024 13:00:02 +0000 Subject: [PATCH 019/134] Links in the navigation bar to be lowercase --- frontend/src/components/header.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx index f80d26c9..ccff79be 100644 --- a/frontend/src/components/header.tsx +++ b/frontend/src/components/header.tsx @@ -19,9 +19,9 @@ import { cn } from '@/lib/classnames'; import ArrowRight from '@/styles/icons/arrow-right.svg?sprite'; const NAVIGATION_ITEMS = [ - { name: 'Progress Tracker', href: PAGES.progressTracker, colorClassName: 'text-orange' }, - { name: 'Conservation Builder', href: PAGES.conservationBuilder, colorClassName: 'text-blue' }, - { name: 'Knowledge Hub', href: PAGES.knowledgeHub, colorClassName: 'text-green' }, + { name: 'Progress tracker', href: PAGES.progressTracker, colorClassName: 'text-orange' }, + { name: 'Conservation builder', href: PAGES.conservationBuilder, colorClassName: 'text-blue' }, + { name: 'Knowledge hub', href: PAGES.knowledgeHub, colorClassName: 'text-green' }, { name: 'About', href: PAGES.about, colorClassName: 'text-violet' }, { name: 'Contact', href: PAGES.contact, colorClassName: 'text-black' }, ]; From 6a4572a26a4e8ecb94820a1fefff45334de34691 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Mon, 25 Mar 2024 13:27:09 +0000 Subject: [PATCH 020/134] Map Sidebar to infer the tool/component based on type, not on store --- frontend/src/containers/map/sidebar/index.tsx | 51 +++++-------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/frontend/src/containers/map/sidebar/index.tsx b/frontend/src/containers/map/sidebar/index.tsx index 694f6a67..38976850 100644 --- a/frontend/src/containers/map/sidebar/index.tsx +++ b/frontend/src/containers/map/sidebar/index.tsx @@ -1,34 +1,27 @@ -import { useCallback } from 'react'; - import { useAtom } from 'jotai'; import { LuChevronLeft } from 'react-icons/lu'; import { Button } from '@/components/ui/button'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; -import { modellingAtom, sidebarAtom } from '@/containers/map/store'; +import { sidebarAtom } from '@/containers/map/store'; import { cn } from '@/lib/classnames'; -import { useSyncMapContentSettings } from '../sync-settings'; - import Details from './details'; import Modelling from './modelling'; -const MapSidebar: React.FC = () => { - const [isSidebarOpen, setSidebarOpen] = useAtom(sidebarAtom); - const [{ active: isModellingActive }, setModellingActive] = useAtom(modellingAtom); - const [{ showDetails }] = useSyncMapContentSettings(); +const SIDEBAR_COMPONENTS = { + 'progress-tracker': Details, + 'conservation-builder': Modelling, +}; + +type MapSidebarProps = { + type: 'progress-tracker' | 'conservation-builder'; +}; - const onClickModelling = useCallback(() => { - setModellingActive((prevState) => ({ - ...prevState, - active: true, - })); - }, [setModellingActive]); +const MapSidebar: React.FC = ({ type = 'progress-tracker' }) => { + const [isSidebarOpen, setSidebarOpen] = useAtom(sidebarAtom); - const modellingFeatureActive = process.env.NEXT_PUBLIC_FEATURE_FLAG_ANALYSIS === 'true'; - const showModellingButton = modellingFeatureActive && !isModellingActive && !showDetails; - const showModellingSidebar = modellingFeatureActive && isModellingActive; - const showDetailsSidebar = !showModellingSidebar; + const ContentComponent = SIDEBAR_COMPONENTS[type] || Details; return ( { open={isSidebarOpen} onOpenChange={setSidebarOpen} > - {showModellingButton && ( - - )} - {showModellingSidebar && } - {showDetailsSidebar &&
} + ); From e89ae5bdc439b104e37c0338b5a41dd4ac391c7e Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Mon, 25 Mar 2024 13:05:49 +0000 Subject: [PATCH 021/134] Move base progress tracker to its own page, in order to match the navigation links --- frontend/next.config.js | 4 ++-- .../src/pages/{map => progress-tracker}/[locationCode].tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename frontend/src/pages/{map => progress-tracker}/[locationCode].tsx (94%) diff --git a/frontend/next.config.js b/frontend/next.config.js index 886053a2..eac27a57 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -38,8 +38,8 @@ const nextConfig = { redirects() { return [ { - source: '/map', - destination: '/map/GLOB', + source: '/progress-tracker', + destination: '/progress-tracker/GLOB', permanent: false, }, ]; diff --git a/frontend/src/pages/map/[locationCode].tsx b/frontend/src/pages/progress-tracker/[locationCode].tsx similarity index 94% rename from frontend/src/pages/map/[locationCode].tsx rename to frontend/src/pages/progress-tracker/[locationCode].tsx index f9607402..aab82569 100644 --- a/frontend/src/pages/map/[locationCode].tsx +++ b/frontend/src/pages/progress-tracker/[locationCode].tsx @@ -46,11 +46,11 @@ export default function Page({ location }: { location: Location }) { return (
- +
- +
); From 1056e49edc95f41aee13c996f6eab6ab9e1ae848 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Mon, 25 Mar 2024 13:38:03 +0000 Subject: [PATCH 022/134] Move Conservation builder to its own page --- .../src/pages/conservation-builder/index.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 frontend/src/pages/conservation-builder/index.tsx diff --git a/frontend/src/pages/conservation-builder/index.tsx b/frontend/src/pages/conservation-builder/index.tsx new file mode 100644 index 00000000..6ec3e14e --- /dev/null +++ b/frontend/src/pages/conservation-builder/index.tsx @@ -0,0 +1,17 @@ +import Content from '@/containers/map/content'; +import Sidebar from '@/containers/map/sidebar'; +import Layout from '@/layouts/map'; + +export default function Page() { + return ( + +
+ +
+ +
+ +
+
+ ); +} From 4b6f8f8aa4950dfacc3831ee556a8b1dc2b98b8f Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Mon, 25 Mar 2024 13:42:00 +0000 Subject: [PATCH 023/134] Remove conservation builder 'Back to Dashboard' button - no longer in use --- .../sidebar/modelling/back-button/index.tsx | 30 ------------------- .../map/sidebar/modelling/index.tsx | 4 --- 2 files changed, 34 deletions(-) delete mode 100644 frontend/src/containers/map/sidebar/modelling/back-button/index.tsx diff --git a/frontend/src/containers/map/sidebar/modelling/back-button/index.tsx b/frontend/src/containers/map/sidebar/modelling/back-button/index.tsx deleted file mode 100644 index c34647b8..00000000 --- a/frontend/src/containers/map/sidebar/modelling/back-button/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { useCallback } from 'react'; - -import { useResetAtom } from 'jotai/utils'; -import { LuChevronLeft } from 'react-icons/lu'; - -import { Button } from '@/components/ui/button'; -import { modellingAtom, drawStateAtom } from '@/containers/map/store'; - -const BackButton: React.FC = () => { - const resetModelling = useResetAtom(modellingAtom); - const resetDrawState = useResetAtom(drawStateAtom); - - const onClickBack = useCallback(() => { - resetDrawState(); - resetModelling(); - }, [resetModelling, resetDrawState]); - - return ( - - ); -}; - -export default BackButton; diff --git a/frontend/src/containers/map/sidebar/modelling/index.tsx b/frontend/src/containers/map/sidebar/modelling/index.tsx index 145bf45c..cd03b614 100644 --- a/frontend/src/containers/map/sidebar/modelling/index.tsx +++ b/frontend/src/containers/map/sidebar/modelling/index.tsx @@ -2,7 +2,6 @@ import { useAtomValue } from 'jotai'; import { modellingAtom } from '../../store'; -import BackButton from './back-button'; import ModellingButtons from './modelling-buttons'; import ModellingIntro from './modelling-intro'; import ModellingWidget from './widget'; @@ -25,9 +24,6 @@ const SidebarModelling: React.FC = () => { {showIntro && } {!showIntro && }
-
- -
); }; From b329639ff190329e7f6f0b71556edd1b8b07c5e5 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Mon, 25 Mar 2024 13:53:06 +0000 Subject: [PATCH 024/134] Fix map crash when rendering if location code param does not exist --- frontend/src/containers/map/content/map/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/containers/map/content/map/index.tsx b/frontend/src/containers/map/content/map/index.tsx index 0c08eb1c..ba515029 100644 --- a/frontend/src/containers/map/content/map/index.tsx +++ b/frontend/src/containers/map/content/map/index.tsx @@ -37,11 +37,13 @@ const MainMap: React.FC = () => { const drawState = useAtomValue(drawStateAtom); const isSidebarOpen = useAtomValue(sidebarAtom); const [popup, setPopup] = useAtom(popupAtom); - const { locationCode } = useParams(); + const params = useParams(); const locationBbox = useAtomValue(bboxLocation); const hoveredPolygonId = useRef[0] | null>(null); const [cursor, setCursor] = useState<'grab' | 'crosshair' | 'pointer'>('grab'); + const locationCode = params?.locationCode || 'GLOB'; + const locationsQuery = useGetLocations( { filters: { From cf2fa6e0721aba6cc48761803554a49eaac900f2 Mon Sep 17 00:00:00 2001 From: Simao Rodrigues Date: Tue, 26 Mar 2024 10:57:13 +0000 Subject: [PATCH 025/134] Add initial proper NextJS per-page layout support + initial application for main app (map) --- frontend/next.config.js | 3 +- frontend/src/pages/_app.tsx | 29 ++++++++++++++++--- .../src/pages/conservation-builder/index.tsx | 15 +++++++--- .../pages/progress-tracker/[locationCode].tsx | 17 +++++++---- 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/frontend/next.config.js b/frontend/next.config.js index eac27a57..b171ab6d 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -35,12 +35,11 @@ const nextConfig = { return config; }, - redirects() { + rewrites() { return [ { source: '/progress-tracker', destination: '/progress-tracker/GLOB', - permanent: false, }, ]; }, diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index 4da5ce4e..5239a909 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -12,15 +12,34 @@ import 'mapbox-gl/dist/mapbox-gl.css'; import Analytics from '@/components/analytics'; import { figtree, overpassMono } from '@/styles/fonts'; -type PageProps = { +type LayoutProps> = { + Component?: React.FC; + props?: Props | ((props: PageProps) => Props); +}; + +type Props = AppProps & { dehydratedState: unknown; + Component: { + layout?: LayoutProps; + }; }; -const MyApp = ({ Component, pageProps }: AppProps) => { +const App: React.FC = ({ Component, pageProps }: Props) => { // Never ever instantiate the client outside a component, hook or callback as it can leak data // between users const [queryClient] = useState(() => new QueryClient()); + const Layout = Component?.layout?.Component ?? ((page) => page?.children); + + let layoutProps = {}; + if (Component.layout?.props) { + if (typeof Component.layout.props === 'function') { + layoutProps = Component.layout.props(pageProps); + } else { + layoutProps = Component.layout.props; + } + } + return ( <>