From 022498c41bc014671bcde1ebd752e958cf58b8f3 Mon Sep 17 00:00:00 2001 From: Maja Komel Date: Wed, 18 Oct 2023 11:19:05 +0200 Subject: [PATCH] Incident reporting platform (#862) --- .env.development | 2 +- .eslintrc.json | 12 +- .prettierrc | 5 + .vscode/settings.json | 3 +- components/Badge.js | 24 +- components/ButtonSpinner.js | 16 + components/Chart.js | 1 - components/DomainChart.js | 1 - components/FormattedMarkdown.js | 15 +- components/MATChart.js | 112 +++++ components/NavBar.js | 23 +- components/NotFound.js | 26 ++ components/SharedStyledComponents.js | 2 +- components/aggregation/mat/ChartHeader.js | 2 +- components/aggregation/mat/GridChart.js | 6 +- components/aggregation/mat/MATContext.js | 40 +- components/aggregation/mat/Resizable.js | 30 +- components/aggregation/mat/RowChart.js | 8 +- components/aggregation/mat/StackedBarChart.js | 6 +- components/aggregation/mat/TableView.js | 1 - components/dashboard/Charts.js | 1 - components/dashboard/Form.js | 81 ++-- components/domain/Form.js | 12 +- components/incidents/Form.js | 247 +++++++++++ components/incidents/ReportDisplay.js | 58 +++ components/landing/HighlightBox.js | 104 +++-- components/landing/HighlightsSection.js | 42 +- components/measurement/MeasurementNotFound.js | 32 -- .../measurement/nettests/WebConnectivity.js | 1 - cypress/e2e/incidents.e2e.cy.js | 94 ++++ cypress/e2e/measurement.e2e.cy.js | 14 +- cypress/mocks/handlers.js | 18 +- cypress/support/msw.js | 28 +- hooks/useUser.js | 41 +- lib/api.js | 77 ++-- package.json | 25 +- pages/chart/mat.js | 141 ++---- pages/incidents/[incident_id].js | 37 ++ pages/incidents/create.js | 57 +++ pages/incidents/dashboard.js | 198 +++++++++ pages/incidents/edit/[incident_id].js | 87 ++++ pages/incidents/index.js | 146 +++++++ pages/m/[measurement_uid].js | 14 +- pages/measurement/[[...report_id]].js | 6 +- public/static/lang/en.json | 33 +- public/static/lang/pt-BR.json | 2 +- utils/index.js | 10 +- yarn.lock | 402 +++++------------- 48 files changed, 1638 insertions(+), 705 deletions(-) create mode 100644 .prettierrc create mode 100644 components/ButtonSpinner.js create mode 100644 components/MATChart.js create mode 100644 components/NotFound.js create mode 100644 components/incidents/Form.js create mode 100644 components/incidents/ReportDisplay.js delete mode 100644 components/measurement/MeasurementNotFound.js create mode 100644 cypress/e2e/incidents.e2e.cy.js create mode 100644 pages/incidents/[incident_id].js create mode 100644 pages/incidents/create.js create mode 100644 pages/incidents/dashboard.js create mode 100644 pages/incidents/edit/[incident_id].js create mode 100644 pages/incidents/index.js diff --git a/.env.development b/.env.development index d930c3dc5..c1c502786 100644 --- a/.env.development +++ b/.env.development @@ -2,7 +2,7 @@ # To override locally, make a copy called `.env.development.local` # Refer: https://nextjs.org/docs/basic-features/environment-variables -NEXT_PUBLIC_OONI_API=https://api.ooni.io +NEXT_PUBLIC_OONI_API=https://ams-pg-test.ooni.org NEXT_PUBLIC_USER_FEEDBACK_API=https://ams-pg-test.ooni.org NEXT_PUBLIC_EXPLORER_URL=http://localhost:3100 diff --git a/.eslintrc.json b/.eslintrc.json index ac0762318..96ba9dcc1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,13 +1,15 @@ { + "root": true, "plugins": ["cypress"], - "extends": [ - "next/core-web-vitals", - "plugin:cypress/recommended" - ], + "extends": ["next/core-web-vitals", "plugin:cypress/recommended", "prettier"], "rules": { "linebreak-style": ["error", "unix"], "quotes": ["error", "single"], "semi": ["error", "never"] }, - "ignorePatterns": ["components/vendor", "static"] + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "ignorePatterns": ["node_modules", "components/vendor", "static"] } diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..b52a92b10 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "semi": false, + "printWidth": 100 +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 59eb1da5f..1f6783464 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { + "prettier.singleQuote": false, "prettier.semi": false, - "prettier.singleQuote": true + "eslint.validate": ["javascript"] } diff --git a/components/Badge.js b/components/Badge.js index 12c7d8411..0c01893cf 100644 --- a/components/Badge.js +++ b/components/Badge.js @@ -7,30 +7,28 @@ import { getTestMetadata } from './utils' import * as icons from 'ooni-components/icons' // XXX replace what is inside of search/results-list.StyledResultTag -const Badge = styled(Box)` +export const Badge = styled(Box)` display: inline-block; border-radius: 4px; padding: 4px 8px; line-height: 16px; font-size: 12px; text-transform: uppercase; - background-color: ${props => props.bg || props.theme.colors.gray8}; - border: ${props => props.borderColor ? `1px solid ${props.borderColor}` : 'none'}; - color: ${props => props.color || props.theme.colors.white}; + background-color: ${(props) => props.bg || props.theme.colors.gray8}; + border: ${(props) => (props.borderColor ? `1px solid ${props.borderColor}` : 'none')}; + color: ${(props) => props.color || props.theme.colors.white}; letter-spacing: 1.25px; font-weight: 600; ` const TestGroupBadge = ({ testName, ...props }) => { - const {icon, groupName, color} = getTestMetadata(testName) + const { icon, groupName, color } = getTestMetadata(testName) return ( - - - - {groupName} - - {cloneElement(icon, {size: 12})} + + + {groupName} + {cloneElement(icon, { size: 12 })} ) @@ -45,8 +43,8 @@ export const CategoryBadge = ({ categoryCode }) => { } return ( - - + + diff --git a/components/ButtonSpinner.js b/components/ButtonSpinner.js new file mode 100644 index 000000000..f8ff7aca7 --- /dev/null +++ b/components/ButtonSpinner.js @@ -0,0 +1,16 @@ +import { ImSpinner8 } from 'react-icons/im' +import { keyframes, styled } from 'styled-components' + +const spin = keyframes` +to { + transform: rotate(360deg); +} +` + +const StyledSpinner = styled(ImSpinner8)` + animation: ${spin} 1s linear infinite; +` + +const ButtonSpinner = () => + +export default ButtonSpinner diff --git a/components/Chart.js b/components/Chart.js index 71027b9a8..8525eadf0 100644 --- a/components/Chart.js +++ b/components/Chart.js @@ -88,7 +88,6 @@ const Chart = React.memo(function Chart({testGroup = null, queryParams = {}, set data={chartData} rowKeys={rowKeys} rowLabels={rowLabels} - isGrouped={false} /> {!!chartData?.size && } diff --git a/components/DomainChart.js b/components/DomainChart.js index 7d7abeba0..2a6bd2b43 100644 --- a/components/DomainChart.js +++ b/components/DomainChart.js @@ -65,7 +65,6 @@ const Chart = React.memo(function Chart({testGroup = null, queryParams = {}, set data={chartData} rowKeys={rowKeys} rowLabels={rowLabels} - isGrouped={false} /> {!!chartData?.size && } diff --git a/components/FormattedMarkdown.js b/components/FormattedMarkdown.js index 887566573..7c1a814dc 100644 --- a/components/FormattedMarkdown.js +++ b/components/FormattedMarkdown.js @@ -6,8 +6,7 @@ import { useIntl } from 'react-intl' import Markdown from 'markdown-to-jsx' import { Link, theme } from 'ooni-components' -const FormattedMarkdown = ({ id, defaultMessage, values }) => { - const intl = useIntl() +export const FormattedMarkdownBase = ({ children }) => { return ( { } }} > - {intl.formatMessage({id, defaultMessage}, values )} + {children} ) } +const FormattedMarkdown = ({ id, defaultMessage, values }) => { + const intl = useIntl() + + return ( + + {intl.formatMessage({id, defaultMessage}, values )} + + ) +} + FormattedMarkdown.propTypes = { id: PropTypes.string.isRequired, defaultMessage: PropTypes.string, diff --git a/components/MATChart.js b/components/MATChart.js new file mode 100644 index 000000000..fb0645f4c --- /dev/null +++ b/components/MATChart.js @@ -0,0 +1,112 @@ +import { Box, Text } from 'ooni-components' +import { StackedBarChart } from 'components/aggregation/mat/StackedBarChart' +import { FunnelChart } from 'components/aggregation/mat/FunnelChart' +import { NoCharts } from 'components/aggregation/mat/NoCharts' +import TableView from 'components/aggregation/mat/TableView' +import { useMemo } from 'react' +import useSWR from 'swr' +import dayjs from 'services/dayjs' +import { axiosResponseTime } from 'components/axios-plugins' +import axios from 'axios' +import { MATContextProvider } from 'components/aggregation/mat/MATContext' +import { useIntl } from 'react-intl' +import { ResizableBox } from './aggregation/mat/Resizable' +import { FormattedMarkdownBase } from './FormattedMarkdown' + +axiosResponseTime(axios) + +const swrOptions = { + revalidateOnFocus: false, + dedupingInterval: 10 * 60 * 1000, +} + +const fetcher = (query) => { + const qs = new URLSearchParams(query).toString() + const reqUrl = `${process.env.NEXT_PUBLIC_OONI_API}/api/v1/aggregation?${qs}` + console.debug(`API Query: ${reqUrl}`) + return axios + .get(reqUrl) + .then((r) => { + return { + data: r.data, + loadTime: r.loadTime, + url: r.config.url, + } + }) + .catch((e) => { + // throw new Error(e?.response?.data?.error ?? e.message) + const error = new Error('An error occurred while fetching the data.') + // Attach extra info to the error object. + error.info = e.response.data.error + error.status = e.response.status + throw error + }) +} + +export const MATChartReportWrapper = ({link, caption}) => { + let searchParams + const today = dayjs.utc().add(1, 'day') + const monthAgo = dayjs.utc(today).subtract(1, 'month') + + try { + if (link) searchParams = Object.fromEntries(new URL(link).searchParams) + } catch (e) { + console.log('e', link, e) + searchParams = null + } + + //TODO: make sure searchParams are only the ones that are allowed + const query = { + test_name: 'web_connectivity', + axis_x: 'measurement_start_day', + since: monthAgo.format('YYYY-MM-DD'), + until: today.format('YYYY-MM-DD'), + time_grain: 'day', + ...searchParams, + } + + return !!searchParams && + + + {caption && ( + + {caption} + + )} + +} + +const MATChart = ({ query }) => { + const intl = useIntl() + const { data, error, isValidating } = useSWR(query ? query : null, fetcher, swrOptions) + + const showLoadingIndicator = useMemo(() => isValidating, [isValidating]) + return ( + + + {error && } + + {showLoadingIndicator ? ( + +

{intl.formatMessage({ id: 'General.Loading' })}

+
+ ) : ( + <> + {data?.data?.result?.length > 0 ? + + {data && data.data.dimension_count == 0 && } + {data && data.data.dimension_count == 1 && } + {data && data.data.dimension_count > 1 && ( + + )} + : + } + + )} +
+
+
+ ) +} + +export default MATChart diff --git a/components/NavBar.js b/components/NavBar.js index 9053c5882..44eb7794b 100644 --- a/components/NavBar.js +++ b/components/NavBar.js @@ -106,21 +106,28 @@ export const NavBar = ({ color }) => { - } href='/search' /> - } href='/chart/mat' /> - } href='/chart/circumvention' /> - } href='/countries' /> - } href='/networks' /> - } href='/domains' /> - {user?.logged_in && ( + } href="/search" /> + } href="/chart/mat" /> + } + href="/chart/circumvention" + /> + } href="/countries" /> + } href="/networks" /> + } href="/domains" /> + {/* } href="/incidents" /> */} + {user?.logged_in ? ( - + + ) : ( + <> + // } href="/login" /> )} diff --git a/components/NotFound.js b/components/NotFound.js new file mode 100644 index 000000000..e3e599982 --- /dev/null +++ b/components/NotFound.js @@ -0,0 +1,26 @@ +/* global process */ +import React from 'react' +import { Container, Flex, Box, Heading, Text } from 'ooni-components' +import { useRouter } from 'next/router' + +import OONI404 from '../public/static/images/OONI_404.svg' +import { useIntl } from 'react-intl' + +const NotFound = ({ title }) => { + const { asPath } = useRouter() + const intl = useIntl() + + return ( + + + + + {title} + {`${process.env.NEXT_PUBLIC_EXPLORER_URL}${asPath}`} + + + + ) +} + +export default NotFound diff --git a/components/SharedStyledComponents.js b/components/SharedStyledComponents.js index 57f68992a..82855c012 100644 --- a/components/SharedStyledComponents.js +++ b/components/SharedStyledComponents.js @@ -17,7 +17,7 @@ z-index: 100; export const StyledStickySubMenu = styled.div` position: sticky; -top: 65.5px; +top: 66px; background: white; z-index: 100; border-bottom: 1px solid ${props => props.theme.colors.gray3}; diff --git a/components/aggregation/mat/ChartHeader.js b/components/aggregation/mat/ChartHeader.js index bc8dbe050..b7eef6d9a 100644 --- a/components/aggregation/mat/ChartHeader.js +++ b/components/aggregation/mat/ChartHeader.js @@ -85,7 +85,7 @@ export const ChartHeader = ({ options = {}}) => { } - + {options.legend !== false && diff --git a/components/aggregation/mat/GridChart.js b/components/aggregation/mat/GridChart.js index f5376b818..944c0aa19 100644 --- a/components/aggregation/mat/GridChart.js +++ b/components/aggregation/mat/GridChart.js @@ -78,9 +78,6 @@ export const prepareDataForGridChart = (data, query, locale) => { * * selectedRows - a subset of `rowKeys` representing which rows to render in the grid * - * isGrouped - Whether the data is already grouped by y-axis value - * If `false`, `reshapeChartData()` will group the data as required - * * height - uses a specific height provided by the container (e.g ResizableBox) * If not speicied, it calculates a height based on the number of rows, capped * at GRID_MAX_HEIGHT, which allows to render a subset of the data @@ -89,7 +86,7 @@ export const prepareDataForGridChart = (data, query, locale) => { * header - an element showing some summary information on top of the charts } */ -const GridChart = ({ data, rowKeys, rowLabels, isGrouped = true, height = 'auto', header, selectedRows = null, noLabels = false }) => { +const GridChart = ({ data, rowKeys, rowLabels, height = 'auto', header, selectedRows = null, noLabels = false }) => { // Fetch query state from context instead of router // because some params not present in the URL are injected in the context @@ -178,7 +175,6 @@ GridChart.propTypes = { rowKeys: PropTypes.arrayOf(PropTypes.string), rowLabels: PropTypes.objectOf(PropTypes.string), selectedRows: PropTypes.arrayOf(PropTypes.string), - isGrouped: PropTypes.bool, height: PropTypes.oneOfType([ PropTypes.string, PropTypes.number diff --git a/components/aggregation/mat/MATContext.js b/components/aggregation/mat/MATContext.js index 5d6d0f8ff..edd8b8712 100644 --- a/components/aggregation/mat/MATContext.js +++ b/components/aggregation/mat/MATContext.js @@ -14,39 +14,45 @@ export const defaultMATContext = { input: '', domain: '', category_code: '', - tooltipIndex: [-1, ''] + tooltipIndex: [-1, ''], } -export const MATContextProvider = ({ children, ...initialContext }) => { - const [state, setState] = useState({...defaultMATContext, ...initialContext}) +export const MATContextProvider = ({ children, queryParams, ...initialContext }) => { + const [state, setState] = useState({ + ...defaultMATContext, + ...initialContext, + }) const { query } = useRouter() - const stateReducer = useCallback((updates, partial = false) => { - setState(state => - partial ? ( - Object.assign({}, state, updates) - ) : ( - Object.assign({}, state, defaultMATContext, initialContext, updates) + const MATquery = queryParams || query + + const stateReducer = useCallback( + (updates, partial = false) => { + setState((state) => + partial + ? Object.assign({}, state, updates) + : Object.assign({}, state, defaultMATContext, initialContext, updates) ) - ) - }, [initialContext]) + }, + [initialContext] + ) useEffect(() => { - stateReducer(query) - }, [query]) + stateReducer(MATquery) + }, [MATquery]) return ( - + {children} ) } -export function useMATContext () { - const {updateMATContext, ...state} = useContext(MATStateContext) +export function useMATContext() { + const { updateMATContext, ...state } = useContext(MATStateContext) if (typeof state === 'undefined') { throw new Error('useMATContext should be used within a MATContextProvider') } return [state, updateMATContext] -} \ No newline at end of file +} diff --git a/components/aggregation/mat/Resizable.js b/components/aggregation/mat/Resizable.js index f173d56e2..795ec1d14 100644 --- a/components/aggregation/mat/Resizable.js +++ b/components/aggregation/mat/Resizable.js @@ -1,29 +1,31 @@ -import React, { useEffect, useCallback, useState } from 'react' import { Box } from 'ooni-components' import styled from 'styled-components' -import { useResizeDetector } from 'react-resize-detector' +// import { useResizeDetector } from 'react-resize-detector' export const ResizableYBox = styled(Box)` position: relative; - border: 2px solid ${props => props.theme.colors.gray1}; + // border: 2px solid ${props => props.theme.colors.gray1}; /* Disabled resizability because of the onResize loop bug */ /* resize: vertical; */ - min-height: ${props => props.minHeight ?? 250}px; + // min-height: ${props => props.minHeight ?? 250}px; /* max-height: 100vh; */ - padding: 16px; + // padding: 16px; ` export const ResizableBox = ({ children, onResize, ...props}) => { - const { width, height, ref } = useResizeDetector({ - onResize, - handleWidth: false, - skipOnMount: true, - refreshMode: 'debounce', - refreshRate: 500, - }) + // const { width, height, ref } = useResizeDetector({ + // onResize, + // handleWidth: false, + // skipOnMount: true, + // refreshMode: 'debounce', + // refreshRate: 500, + // }) return ( - + // + // {children} + // + {children} - + ) } diff --git a/components/aggregation/mat/RowChart.js b/components/aggregation/mat/RowChart.js index bf6cfb3d9..0889fd9be 100644 --- a/components/aggregation/mat/RowChart.js +++ b/components/aggregation/mat/RowChart.js @@ -46,10 +46,10 @@ const chartProps1D = (query, intl) => ({ round: false }, margin: { - top: 50, - right: 30, - bottom: 100, - left: 80 + top: 30, + right: 20, + bottom: 80, + left: 70 }, padding: 0.3, borderColor: { from: 'color', modifiers: [ [ 'darker', 1.6 ] ] }, diff --git a/components/aggregation/mat/StackedBarChart.js b/components/aggregation/mat/StackedBarChart.js index e046f8346..01aded68b 100644 --- a/components/aggregation/mat/StackedBarChart.js +++ b/components/aggregation/mat/StackedBarChart.js @@ -9,15 +9,15 @@ import { NoCharts } from './NoCharts' const ChartContainer = styled(Flex)` position: relative; - border: 2px solid ${props => props.theme.colors.gray1}; - padding: 16px; + // border: 2px solid ${props => props.theme.colors.gray1}; + // padding: 16px; ` export const StackedBarChart = ({ data, query }) => { const intl = useIntl() try { - const [gridData, rows ] = prepareDataForGridChart(data.data.result, query, intl.locale) + const [gridData, rows ] = prepareDataForGridChart(data, query, intl.locale) return ( diff --git a/components/aggregation/mat/TableView.js b/components/aggregation/mat/TableView.js index 36bb142a0..0d6d24283 100644 --- a/components/aggregation/mat/TableView.js +++ b/components/aggregation/mat/TableView.js @@ -71,7 +71,6 @@ const TableView = ({ data, query }) => { selectedRows={dataForCharts} rowKeys={rowKeys} rowLabels={rowLabels} - isGrouped={true} /> diff --git a/components/dashboard/Charts.js b/components/dashboard/Charts.js index 61621475e..a47d01e31 100644 --- a/components/dashboard/Charts.js +++ b/components/dashboard/Charts.js @@ -102,7 +102,6 @@ const Chart = React.memo(function Chart({ testName }) { data={chartData} rowKeys={rowKeys} rowLabels={rowLabels} - isGrouped={false} header={headerOptions} /> )} diff --git a/components/dashboard/Form.js b/components/dashboard/Form.js index c79f1be7b..9cba051d3 100644 --- a/components/dashboard/Form.js +++ b/components/dashboard/Form.js @@ -10,13 +10,16 @@ import DateRangePicker from '../DateRangePicker' export const Form = ({ onChange, query, availableCountries }) => { const intl = useIntl() - const countryOptions = useMemo(() => availableCountries - .map(cc => ({ - label: getLocalisedRegionName(cc, intl.locale), - value: cc - })) - .sort((a, b) => (new Intl.Collator(intl.locale).compare(a.label, b.label))) - , [availableCountries, intl]) + const countryOptions = useMemo( + () => + availableCountries + .map((cc) => ({ + label: getLocalisedRegionName(cc, intl.locale), + value: cc, + })) + .sort((a, b) => new Intl.Collator(intl.locale).compare(a.label, b.label)), + [availableCountries, intl] + ) const query2formValues = useMemo(() => { const countriesInQuery = query.probe_cc?.split(',') ?? '' @@ -27,23 +30,34 @@ export const Form = ({ onChange, query, availableCountries }) => { } }, [countryOptions, query]) - const multiSelectStrings = useMemo(() => ({ - 'allItemsAreSelected': intl.formatMessage({ id: 'ReachabilityDash.Form.Label.CountrySelect.AllSelected' }), - // 'clearSearch': 'Clear Search', - // 'clearSelected': 'Clear Selected', - // 'noOptions': 'No options', - 'search': intl.formatMessage({ id: 'ReachabilityDash.Form.Label.CountrySelect.SearchPlaceholder' }), - 'selectAll': intl.formatMessage({ id: 'ReachabilityDash.Form.Label.CountrySelect.SelectAll' }), - 'selectAllFiltered': intl.formatMessage({ id: 'ReachabilityDash.Form.Label.CountrySelect.SelectAllFiltered' }), - 'selectSomeItems': intl.formatMessage({ id: 'ReachabilityDash.Form.Label.CountrySelect.InputPlaceholder' }), - // 'create': 'Create', - }), [intl]) + const multiSelectStrings = useMemo( + () => ({ + allItemsAreSelected: intl.formatMessage({ + id: 'ReachabilityDash.Form.Label.CountrySelect.AllSelected', + }), + // 'clearSearch': 'Clear Search', + // 'clearSelected': 'Clear Selected', + // 'noOptions': 'No options', + search: intl.formatMessage({ + id: 'ReachabilityDash.Form.Label.CountrySelect.SearchPlaceholder', + }), + selectAll: intl.formatMessage({ id: 'ReachabilityDash.Form.Label.CountrySelect.SelectAll' }), + selectAllFiltered: intl.formatMessage({ + id: 'ReachabilityDash.Form.Label.CountrySelect.SelectAllFiltered', + }), + selectSomeItems: intl.formatMessage({ + id: 'ReachabilityDash.Form.Label.CountrySelect.InputPlaceholder', + }), + // 'create': 'Create', + }), + [intl] + ) const { control, getValues, watch, setValue, reset } = useForm({ - defaultValues: query2formValues + defaultValues: query2formValues, }) - useEffect(()=> { + useEffect(() => { reset(query2formValues) }, [query2formValues, reset]) @@ -52,7 +66,7 @@ export const Form = ({ onChange, query, availableCountries }) => { return { since, until, - probe_cc: probe_cc.length > 0 ? probe_cc.map(d => d.value).join(',') : undefined + probe_cc: probe_cc.length > 0 ? probe_cc.map((d) => d.value).join(',') : undefined, } } @@ -96,16 +110,16 @@ export const Form = ({ onChange, query, availableCountries }) => { control={control} /> - + - + ( + render={({ field }) => ( setShowDatePicker(true)} onKeyDown={() => setShowDatePicker(false)} name={field.name} @@ -115,13 +129,13 @@ export const Form = ({ onChange, query, availableCountries }) => { )} /> - + ( + render={({ field }) => ( setShowDatePicker(true)} onKeyDown={() => setShowDatePicker(false)} @@ -133,16 +147,15 @@ export const Form = ({ onChange, query, availableCountries }) => { /> - { showDatePicker && + {showDatePicker && ( setShowDatePicker(false)} /> - } + )}
) } - diff --git a/components/domain/Form.js b/components/domain/Form.js index 5638ecebf..6b738332e 100644 --- a/components/domain/Form.js +++ b/components/domain/Form.js @@ -28,7 +28,7 @@ const Form = ({ onSubmit, availableCountries = [] }) => { name: getLocalisedRegionName(c, intl.locale), value: c, })), - [availableCountries, intl.locale] + [availableCountries, intl.locale], ) const query2formValues = useMemo(() => { @@ -97,7 +97,7 @@ const Form = ({ onSubmit, availableCountries = [] }) => { ( { ( { ( + )} + /> + ( + + )} + /> + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + + + } + /> + } + /> + } + /> + } + /> + } + /> + ( + + )} + /> + ( +