diff --git a/web-app/public/locales/en/common.json b/web-app/public/locales/en/common.json index 49fe2127b..3ed83d14a 100644 --- a/web-app/public/locales/en/common.json +++ b/web-app/public/locales/en/common.json @@ -23,5 +23,12 @@ "apiKey": "API Key", "httpHeader": "HTTP Header", "back": "Back", - "and": "and" + "and": "and", + "next": "Next", + "form": { + "yes": "Yes", + "no": "No", + "required": "This field is required", + "submit": "Submit" + } } \ No newline at end of file diff --git a/web-app/public/locales/en/feeds.json b/web-app/public/locales/en/feeds.json index abef49fcb..c4de38b15 100644 --- a/web-app/public/locales/en/feeds.json +++ b/web-app/public/locales/en/feeds.json @@ -2,6 +2,7 @@ "feeds": "Feeds", "dataType": "Data Format", "transitProvider": "Transit Provider", + "transitProviderName": "Transit Provider Name", "location": "Location", "feedDescription": "Description", "searchFor": "Search For", @@ -21,7 +22,12 @@ "addOrUpdateFeed": "Add or Update a Feed", "signUp": "Sign up for a Mobility Database account or login to add or update a GTFS feed.", "signUpAction": "Sign up for an account", - "loginSuccess": "You were successfully logged in, you can now add or update a feed." + "loginSuccess": "You were successfully logged in, you can now add or update a feed.", + "dataTypeRequired": "Data format required", + "feedLinkRequired": "Feed link required", + "oldFeedLinkRequired": "Old feed link required", + "dataProducerEmailRequired": "Data producer email required", + "contactEmailRequired": "Contact email required" }, "seeFullList": "See full list", "hideFullList": "Hide full list", @@ -38,5 +44,18 @@ "downloadLatest": "Download Latest", "seeLicense": "See License", "boundingBoxTitle": "Bounding box from stops.txt", - "unableToGenerateBoundingBox": "Unable to generate bounding box." + "unableToGenerateBoundingBox": "Unable to generate bounding box.", + "areYouOfficialProducer": "Are you the official producer or transit agency responsible for this data ?", + "feedLink": "Feed Link", + "areYouUpdatingFeed": "Are you updating a feed?", + "oldFeedLink": "Old Feed Link", + "dataProducerEmail": "Data Producer Email", + "dataProducerEmailDetails": "This is an official email that consumers of the feed can contact to ask questions.", + "interestedInDataAudit": "Are you interested in a data quality audit?", + "interestedInDataAuditDetails": "This is a 1 time meeting with MobilityData to review your GTFS validation report and discuss possible improvements.", + "dataAuditContactEmail": "Data quality audit contact email", + "hasLogoPermission": "Do we have your permission to use your logo?", + "hasLogoPermissionDetails": "This would be would be used to display your logo on the Mobilitydatabase website", + "whatToolsCreateGtfs": "What tools do you use to create GTFS data?", + "whatToolsCreateGtfsDetails": "Could include open source librareis, vendor serviecs, or other applications." } 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..720a63c17 100644 --- a/web-app/src/app/screens/FeedSubmission/Form/FirstStep.tsx +++ b/web-app/src/app/screens/FeedSubmission/Form/FirstStep.tsx @@ -12,89 +12,98 @@ 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'; +import { useTranslation } from 'react-i18next'; 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 { t } = useTranslation('feeds'); const { control, handleSubmit, 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.isUpdatingFeed; + 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 - - ( - - )} - /> - - - - Are you the official producer or transit agency responsible for - this data ? - + {t('areYouOfficialProducer')} ( @@ -103,12 +112,12 @@ export default function FormFirstStep({ } - label='Yes' + label={t('common:form.yes')} /> } - label='No' + label={t('common:form.no')} /> @@ -124,15 +133,19 @@ export default function FormFirstStep({ component='fieldset' error={errors.dataType !== undefined} > - Data Type + {t('dataType')} ( )} /> @@ -140,7 +153,7 @@ export default function FormFirstStep({ - Transit Provider Name + {t('transitProviderName')} - - - - Feed link - - ( - + + + {t('feedLink')} + + ( + + )} + /> + + + )} + + {dataType === 'gtfs' && ( + <> + + + {t('areYouUpdatingFeed')} + ( + + )} /> - )} - /> - - - - - Link to feed license - ( - - )} - /> - - + + + {isUpdatingFeed === 'yes' && ( + + + + {t('oldFeedLink')} + + ( + + )} + /> + + + )} + + )} diff --git a/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx b/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx index 9067fc7f8..954c46a91 100644 --- a/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx +++ b/web-app/src/app/screens/FeedSubmission/Form/ThirdStep.tsx @@ -7,14 +7,23 @@ 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 '.'; +import { useTranslation } from 'react-i18next'; export interface FeedSubmissionFormInputThirdStep { - dataProducerEmail: string; - isInterestedInQualityAudit: boolean; - whatToolsUsedText: string; + dataProducerEmail?: string; + isInterestedInQualityAudit: YesNoFormInput; + userInterviewEmail?: string; + hasLogoPermission: YesNoFormInput; + whatToolsUsedText?: string; } interface FormSecondStepRTProps { @@ -28,6 +37,7 @@ export default function FormThirdStep({ submitFormData, handleBack, }: FormSecondStepRTProps): React.ReactElement { + const { t } = useTranslation('feeds'); const { control, handleSubmit, @@ -38,11 +48,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 */} @@ -55,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')}
( @@ -77,64 +97,99 @@ 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. - - + + + {t('interestedInDataAudit')} +

+ + {t('interestedInDataAuditDetails')} + +
+ ( + <> + + + {errors.isInterestedInQualityAudit?.message ?? ''} + + + )} + /> +
+
+ {isInterestedInQualityAudit === 'yes' && ( + + + {t('dataAuditContactEmail')} ( - - } - label='Yes' - /> - } - label='No' - /> - + )} /> - */} - - - Are you interested in a data quality audit? + + + )} + + + + {t('hasLogoPermission')}

- This is a 1 time meeting with MobilityData to review your GTFS - validation report and discuss possible improvements. + {t('hasLogoPermissionDetails')}
( - + <> + + + {errors.hasLogoPermission?.message ?? ''} + + )} />
- - - What tools do you use to create GTFS data? Could include open - source libraries, vendor services, or other applications. + + + {t('whatToolsCreateGtfs')} +

+ + {t('whatToolsCreateGtfsDetails')} +
( @@ -158,12 +213,12 @@ export default function FormThirdStep({ variant='outlined' sx={{ mt: 3, mb: 2 }} > - Back + {t('common:back')}
diff --git a/web-app/src/app/screens/FeedSubmission/Form/index.tsx b/web-app/src/app/screens/FeedSubmission/Form/index.tsx index 9137a1989..a543dd0fe 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?: YesNoFormInput; 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')} @@ -95,12 +95,12 @@ function Component(): React.ReactElement { color: colors.blue.A700, fontWeight: 'bold', my: 3, - ml: 5, + ml: 0, }} > Add or update a feed - + )}