From 78c1898527c34b9ba2de9ab22df43c3589ac16e2 Mon Sep 17 00:00:00 2001 From: Alessandro Kreslin Date: Thu, 29 Aug 2024 14:50:26 -0400 Subject: [PATCH 1/3] style and functional add feed form p1-3 --- .../FeedSubmission/FeedSubmissionStepper.tsx | 50 ----- .../screens/FeedSubmission/Form/FirstStep.tsx | 181 +++++++++++------- .../screens/FeedSubmission/Form/ThirdStep.tsx | 145 +++++++++----- .../app/screens/FeedSubmission/Form/index.tsx | 78 ++++++-- .../src/app/screens/FeedSubmission/index.tsx | 10 +- 5 files changed, 281 insertions(+), 183 deletions(-) delete mode 100644 web-app/src/app/screens/FeedSubmission/FeedSubmissionStepper.tsx diff --git a/web-app/src/app/screens/FeedSubmission/FeedSubmissionStepper.tsx b/web-app/src/app/screens/FeedSubmission/FeedSubmissionStepper.tsx deleted file mode 100644 index b908fcf74..000000000 --- a/web-app/src/app/screens/FeedSubmission/FeedSubmissionStepper.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import Stepper from '@mui/material/Stepper'; -import Step from '@mui/material/Step'; -import StepLabel from '@mui/material/StepLabel'; -import FeedSubmissionForm from './Form'; -import { useNavigate } from 'react-router-dom'; - -const steps = ['', '', '']; - -export default function FeedSubmissionStepper(): React.ReactElement { - const [activeStep, setActiveStep] = React.useState(0); - const navigateTo = useNavigate(); - - const handleNext = (): void => { - const nextStep = activeStep + 1; - setActiveStep(nextStep); - if (nextStep === steps.length) { - navigateTo('/contribute/submitted'); - } - }; - - const handleBack = (): void => { - setActiveStep((prevActiveStep) => prevActiveStep - 1); - }; - - return ( - - - {steps.map((label, index) => { - const stepProps: { completed?: boolean } = {}; - const labelProps: { - optional?: React.ReactNode; - } = {}; - return ( - - {label} - - ); - })} - - - - - ); -} diff --git a/web-app/src/app/screens/FeedSubmission/Form/FirstStep.tsx b/web-app/src/app/screens/FeedSubmission/Form/FirstStep.tsx index 29a97824d..5c474a235 100644 --- a/web-app/src/app/screens/FeedSubmission/Form/FirstStep.tsx +++ b/web-app/src/app/screens/FeedSubmission/Form/FirstStep.tsx @@ -12,26 +12,34 @@ import { FormHelperText, } from '@mui/material'; -import { type SubmitHandler, Controller, useForm } from 'react-hook-form'; -import { type FeedSubmissionFormFormInput } from '.'; +import { + type SubmitHandler, + Controller, + useForm, + useWatch, +} from 'react-hook-form'; +import { type YesNoFormInput, type FeedSubmissionFormFormInput } from '.'; +import { useEffect } from 'react'; export interface FeedSubmissionFormFormInputFirstStep { - name: string; - isOfficialProducer: string; + isOfficialProducer: YesNoFormInput; dataType: string; - transitProviderName: string; - feedLink: string; - licensePath: string; + transitProviderName?: string; + feedLink?: string; + oldFeedLink?: string; + isUpdatingFeed: YesNoFormInput; } interface FormFirstStepProps { initialValues: FeedSubmissionFormFormInput; submitFormData: (formData: Partial) => void; + setNumberOfSteps: (numberOfSteps: YesNoFormInput) => void; } export default function FormFirstStep({ initialValues, submitFormData, + setNumberOfSteps, }: FormFirstStepProps): React.ReactElement { const { control, @@ -39,51 +47,52 @@ export default function FormFirstStep({ formState: { errors }, } = useForm({ defaultValues: { - name: initialValues.name, isOfficialProducer: initialValues.isOfficialProducer, dataType: initialValues.dataType, transitProviderName: initialValues.transitProviderName, feedLink: initialValues.feedLink, - licensePath: initialValues.licensePath, + oldFeedLink: initialValues.oldFeedLink, + isUpdatingFeed: initialValues.isUpdatingFeed, }, }); const onSubmit: SubmitHandler = ( data, ): void => { + if (data.dataType === 'gtfs_rt') { + delete data.feedLink; + delete data.oldFeedLink; + } + + if (data.dataType === 'gtfs' && data.isUpdatingFeed === 'no') { + delete data.oldFeedLink; + } submitFormData(data); }; + + const dataType = useWatch({ + control, + name: 'dataType', + }); + + const isUpdatingFeed = useWatch({ + control, + name: 'isUpdatingFeed', + }); + + const isOfficialProducer = useWatch({ + control, + name: 'isOfficialProducer', + }); + + useEffect(() => { + setNumberOfSteps(isOfficialProducer); + }, [isOfficialProducer]); + return ( <> {/* eslint-disable-next-line @typescript-eslint/no-misused-promises */}
- - - - Your Name and (if applicable) Organization - - ( - - )} - /> - - Data Type ( )} /> @@ -150,42 +159,86 @@ export default function FormFirstStep({ /> + {dataType === 'gtfs' && ( + + + + Feed link + + ( + + )} + /> + + + )} + - - Feed link - + Are you updating a feed? ( - - )} - /> - - - - - Link to feed license - ( - + )} /> + {isUpdatingFeed === 'yes' && dataType === 'gtfs' && ( + + + + Old Feed link + + ( + + )} + /> + + + )} diff --git a/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx b/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx index 9067fc7f8..cb020ad09 100644 --- a/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx +++ b/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx @@ -7,14 +7,22 @@ import { TextField, MenuItem, Select, + FormHelperText, } from '@mui/material'; -import { type SubmitHandler, Controller, useForm } from 'react-hook-form'; -import { type FeedSubmissionFormFormInput } from '.'; +import { + type SubmitHandler, + Controller, + useForm, + useWatch, +} from 'react-hook-form'; +import { type YesNoFormInput, type FeedSubmissionFormFormInput } from '.'; export interface FeedSubmissionFormInputThirdStep { - dataProducerEmail: string; - isInterestedInQualityAudit: boolean; - whatToolsUsedText: string; + dataProducerEmail?: string; + isInterestedInQualityAudit: YesNoFormInput; + userInterviewEmail?: string; + hasLogoPermission: YesNoFormInput; + whatToolsUsedText?: string; } interface FormSecondStepRTProps { @@ -38,11 +46,21 @@ export default function FormThirdStep({ dataProducerEmail: initialValues.dataProducerEmail, isInterestedInQualityAudit: initialValues.isInterestedInQualityAudit, whatToolsUsedText: initialValues.whatToolsUsedText, + hasLogoPermission: initialValues.hasLogoPermission, }, }); const onSubmit: SubmitHandler = (data) => { + if (data.isInterestedInQualityAudit === 'no') { + delete data.userInterviewEmail; + } submitFormData(data); }; + + const isInterestedInQualityAudit = useWatch({ + control, + name: 'isInterestedInQualityAudit', + }); + return ( <> {/* eslint-disable-next-line @typescript-eslint/no-misused-promises */} @@ -77,36 +95,11 @@ export default function FormThirdStep({ - {/* TODO: UX design decisionz: dropdown or radio buttons? */} - {/* - - Are you interested in a data quality audit? - - This is a 1 time meeting with MobilityData to review your - GTFS validation report and discuss possible improvements. - - - ( - - } - label='Yes' - /> - } - label='No' - /> - - )} - /> - */} - - + + Are you interested in a data quality audit?

@@ -117,24 +110,86 @@ export default function FormThirdStep({ ( + <> + + + {errors.isInterestedInQualityAudit?.message ?? ''} + + + )} + /> +
+
+ {isInterestedInQualityAudit === 'yes' && ( + + + Data quality audit contact email + ( + + )} + /> + + + )} + + + + Do we have your permission to use your logo?

+ + This would be would be used to display your logo on the + Mobilitydatabase website + +
+ ( - + <> + + + {errors.hasLogoPermission?.message ?? ''} + + )} />
- - - What tools do you use to create GTFS data? Could include open - source libraries, vendor services, or other applications. + + + What tools do you use to create GTFS data? +

+ + Could include open source librareis, vendor serviecs, or other + applications. +
( diff --git a/web-app/src/app/screens/FeedSubmission/Form/index.tsx b/web-app/src/app/screens/FeedSubmission/Form/index.tsx index 9137a1989..c80a85c96 100644 --- a/web-app/src/app/screens/FeedSubmission/Form/index.tsx +++ b/web-app/src/app/screens/FeedSubmission/Form/index.tsx @@ -3,12 +3,10 @@ import FormFirstStep from './FirstStep'; import FormSecondStep from './SecondStep'; import FormSecondStepRT from './SecondStepRealtime'; import FormThirdStep from './ThirdStep'; +import { Stepper, Step, StepLabel } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; -export interface FeedSubmissionFormProps { - activeStep: number; - handleBack: () => void; - handleNext: () => void; -} +export type YesNoFormInput = 'yes' | 'no' | ''; // This is the request body required for the API // FeedSubmissionFormFormInput should extend this @@ -41,11 +39,12 @@ export interface FeedSubmissionFormBody { } export interface FeedSubmissionFormFormInput { - name: string; - isOfficialProducer: string; + isOfficialProducer: YesNoFormInput; dataType: string; transitProviderName: string; feedLink: string; + oldFeedLink?: string; + isUpdatingFeed: 'yes' | 'no' | ''; licensePath: string; country: string; region: string; @@ -58,16 +57,19 @@ export interface FeedSubmissionFormFormInput { note: string; isAuthRequired: string; dataProducerEmail: string; - isInterestedInQualityAudit: boolean; - whatToolsUsedText: string; + isInterestedInQualityAudit: YesNoFormInput; + userInterviewEmail?: string; + whatToolsUsedText?: string; + hasLogoPermission: YesNoFormInput; } const defaultFormValues: FeedSubmissionFormFormInput = { - name: '', isOfficialProducer: '', - dataType: 'GTFS Schedule', + dataType: 'gtfs', transitProviderName: '', feedLink: '', + oldFeedLink: '', + isUpdatingFeed: 'no', licensePath: '', country: '', region: '', @@ -80,18 +82,39 @@ const defaultFormValues: FeedSubmissionFormFormInput = { note: '', isAuthRequired: 'no', dataProducerEmail: '', - isInterestedInQualityAudit: false, + isInterestedInQualityAudit: '', + userInterviewEmail: '', whatToolsUsedText: '', + hasLogoPermission: '', }; -export default function FeedSubmissionForm({ - activeStep, - handleNext, - handleBack, -}: FeedSubmissionFormProps): React.ReactElement { +export default function FeedSubmissionForm(): React.ReactElement { + const [activeStep, setActiveStep] = React.useState(0); + const [steps, setSteps] = React.useState(['', '']); + const navigateTo = useNavigate(); const [formData, setFormData] = React.useState(defaultFormValues); + const handleBack = (): void => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + const handleNext = (): void => { + const nextStep = activeStep + 1; + setActiveStep(nextStep); + if (nextStep === steps.length) { + navigateTo('/contribute/submitted'); + } + }; + + const setNumberOfSteps = (isOfficialProducer: YesNoFormInput): void => { + if (isOfficialProducer === 'yes') { + setSteps(['', '', '']); + } else { + setSteps(['', '']); + } + }; + const formStepSubmit = ( partialFormData: Partial, ): void => { @@ -120,20 +143,37 @@ export default function FeedSubmissionForm({ return ( <> + + {steps.map((label, index) => { + const stepProps: { completed?: boolean } = {}; + const labelProps: { + optional?: React.ReactNode; + } = {}; + return ( + + {label} + + ); + })} + {activeStep === 0 && ( )} - {activeStep === 1 && formData.dataType === 'GTFS Schedule' && ( + {activeStep === 1 && formData.dataType === 'gtfs' && ( )} - {activeStep === 1 && formData.dataType === 'GTFS Realtime' && ( + {activeStep === 1 && formData.dataType === 'gtfs_rt' && ( - {t('form.addOrUpdateFeed')} + {t('form.addOrUpdateFeed')} sss {t('form.signUp')}
diff --git a/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx b/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx index cb020ad09..954c46a91 100644 --- a/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx +++ b/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx @@ -16,6 +16,7 @@ import { useWatch, } from 'react-hook-form'; import { type YesNoFormInput, type FeedSubmissionFormFormInput } from '.'; +import { useTranslation } from 'react-i18next'; export interface FeedSubmissionFormInputThirdStep { dataProducerEmail?: string; @@ -36,6 +37,7 @@ export default function FormThirdStep({ submitFormData, handleBack, }: FormSecondStepRTProps): React.ReactElement { + const { t } = useTranslation('feeds'); const { control, handleSubmit, @@ -73,14 +75,14 @@ export default function FormThirdStep({ error={errors.dataProducerEmail !== undefined} > - Data Producer Email

+ {t('dataProducerEmail')} +

- This is an official email that consumers of the feed can - contact to ask questions. + {t('dataProducerEmailDetails')}
( @@ -100,22 +102,21 @@ export default function FormThirdStep({ error={errors.isInterestedInQualityAudit !== undefined} > - Are you interested in a data quality audit? + {t('interestedInDataAudit')}

- This is a 1 time meeting with MobilityData to review your GTFS - validation report and discuss possible improvements. + {t('interestedInDataAuditDetails')}
( <> {errors.isInterestedInQualityAudit?.message ?? ''} @@ -132,11 +133,11 @@ export default function FormThirdStep({ fullWidth error={errors.userInterviewEmail !== undefined} > - Data quality audit contact email + {t('dataAuditContactEmail')} ( - Do we have your permission to use your logo?

+ {t('hasLogoPermission')} +

- This would be would be used to display your logo on the - Mobilitydatabase website + {t('hasLogoPermissionDetails')}
( <> {errors.hasLogoPermission?.message ?? ''} @@ -182,11 +183,10 @@ export default function FormThirdStep({ - What tools do you use to create GTFS data? + {t('whatToolsCreateGtfs')}

- Could include open source librareis, vendor serviecs, or other - applications. + {t('whatToolsCreateGtfsDetails')}
- Back + {t('common:back')}
diff --git a/web-app/src/app/screens/FeedSubmission/index.tsx b/web-app/src/app/screens/FeedSubmission/index.tsx index 895d43cbc..820e2472e 100644 --- a/web-app/src/app/screens/FeedSubmission/index.tsx +++ b/web-app/src/app/screens/FeedSubmission/index.tsx @@ -42,7 +42,7 @@ function Component(): React.ReactElement { color='primary' sx={{ fontWeight: 'bold', ml: 0 }} > - {t('form.addOrUpdateFeed')} sss + {t('form.addOrUpdateFeed')} {t('form.signUp')}