Skip to content

Commit 96a2627

Browse files
authored
Merge branch 'PROD-2321_bug-hunt-intake-form' into PROD-2450_pay-and-complete
2 parents dc68cc9 + bf59292 commit 96a2627

File tree

17 files changed

+178
-74
lines changed

17 files changed

+178
-74
lines changed

src-ts/lib/contact-support-form/contact-support-form.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const contactSupportFormDef: FormDefinition = {
1212
primaryGroup: [
1313
{
1414
buttonStyle: 'secondary',
15-
isSave: true,
15+
isSubmit: true,
1616
label: 'Submit',
1717
size: 'lg',
1818
type: 'submit',

src-ts/lib/form/Form.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Button } from '../button'
1515
import '../styles/index.scss'
1616
import { IconOutline } from '../svgs'
1717

18-
import { FormButton, FormDefinition, FormInputModel } from '.'
18+
import { FormAction, FormButton, FormDefinition, FormInputModel } from '.'
1919
import {
2020
formGetInputFields,
2121
formInitializeValues,
@@ -29,6 +29,7 @@ import { FormGroups } from './form-groups'
2929
import styles from './Form.module.scss'
3030

3131
interface FormProps<ValueType, RequestType> {
32+
readonly action?: FormAction // only type submit will perform validation
3233
readonly formDef: FormDefinition
3334
readonly formValues?: ValueType
3435
readonly onChange?: (inputs: ReadonlyArray<FormInputModel>) => void,
@@ -60,9 +61,12 @@ const Form: <ValueType extends any, RequestType extends any>(props: FormProps<Va
6061
if (!formRef.current?.elements) {
6162
return
6263
}
63-
validateForm(formRef.current?.elements, 'initial', inputs)
64+
65+
// validators do not clear errors on the 'initial' event,
66+
// so we call validateForm as a change here, to support the parent component sending props.formValues updates due to async data loading
67+
validateForm(formRef.current?.elements, 'change', inputs)
6468
checkIfFormIsValid(inputs)
65-
}, [])
69+
}, [props.formValues])
6670

6771
function checkIfFormIsValid(formInputFields: Array<FormInputModel>): void {
6872
setFormInvalid(formInputFields.filter(item => !!item.error).length > 0)
@@ -96,7 +100,7 @@ const Form: <ValueType extends any, RequestType extends any>(props: FormProps<Va
96100

97101
async function onSubmitAsync(event: FormEvent<HTMLFormElement>): Promise<void> {
98102
const values: RequestType = props.requestGenerator(inputs)
99-
formOnSubmitAsync<RequestType>(event, formDef, values, props.save, props.onSuccess)
103+
formOnSubmitAsync<RequestType>(props.action || 'submit', event, formDef, values, props.save, props.onSuccess)
100104
.then(() => {
101105
setFormKey(Date.now())
102106
formOnReset(inputs, props.formValues)
@@ -126,7 +130,7 @@ const Form: <ValueType extends any, RequestType extends any>(props: FormProps<Va
126130
<Button
127131
{...button}
128132
key={button.label || `button-${index}`}
129-
disable={isPrimaryGroup && isFormInvalid}
133+
disable={button.isSubmit && isFormInvalid}
130134
tabIndex={button.notTabble ? -1 : index + (inputs ? inputs.length : 0) + (formDef.tabIndexStart || 0)}
131135
/>
132136
)

src-ts/lib/form/form-button.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export interface FormButton {
66
readonly buttonStyle?: ButtonStyle
77
readonly icon?: FC<SVGProps<SVGSVGElement>>
88
readonly isReset?: boolean
9-
readonly isSave?: boolean
9+
readonly isSubmit?: boolean
1010
readonly label?: string
1111
readonly notTabble?: boolean
1212
onClick?: (event?: any) => void

src-ts/lib/form/form-definition.model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { FormButton, FormGroup } from '.'
22

3+
export type FormAction = 'save' | 'submit' | undefined
4+
35
export interface FormButtons {
46
primaryGroup: ReadonlyArray<FormButton>
57
secondaryGroup?: ReadonlyArray<FormButton>

src-ts/lib/form/form-functions/form.functions.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ChangeEvent, FormEvent } from 'react'
22
import { toast } from 'react-toastify'
33

4-
import { FormDefinition } from '../form-definition.model'
4+
import { FormAction, FormDefinition } from '../form-definition.model'
55
import { FormGroup } from '../form-group.model'
66
import { FormInputModel } from '../form-input.model'
77

@@ -31,11 +31,11 @@ export function getInputModel(inputs: ReadonlyArray<FormInputModel>, fieldName:
3131

3232
export function initializeValues<T>(inputs: Array<FormInputModel>, formValues?: T): void {
3333
inputs
34-
.filter(input => !input.dirty && !input.touched)
34+
.filter(input => !input.dirty && !input.touched)
3535
.forEach(input => {
3636
input.value = !!(formValues as any)?.hasOwnProperty(input.name)
37-
? (formValues as any)[input.name]
38-
: undefined
37+
? (formValues as any)[input.name]
38+
: undefined
3939
})
4040
}
4141

@@ -58,6 +58,7 @@ export function onReset(inputs: ReadonlyArray<FormInputModel>, formValue?: any):
5858
}
5959

6060
export async function onSubmitAsync<T>(
61+
action: FormAction,
6162
event: FormEvent<HTMLFormElement>,
6263
formDef: FormDefinition,
6364
formValue: T,
@@ -78,9 +79,11 @@ export async function onSubmitAsync<T>(
7879
// could have a form that's not dirty but has errors and you wouldn't
7980
// want to have it look like the submit succeeded
8081
const formValues: HTMLFormControlsCollection = (event.target as HTMLFormElement).elements
81-
const isValid: boolean = validateForm(formValues, 'submit', inputs)
82-
if (!isValid) {
83-
return Promise.reject()
82+
if (action === 'submit') {
83+
const isValid: boolean = validateForm(formValues, action, inputs)
84+
if (!isValid) {
85+
return Promise.reject()
86+
}
8487
}
8588

8689
// set the properties for the updated T value
@@ -170,9 +173,9 @@ function validateField(formInputDef: FormInputModel, formElements: HTMLFormContr
170173

171174
export function validateForm(formElements: HTMLFormControlsCollection, event: 'blur' | 'change' | 'submit' | 'initial', inputs: ReadonlyArray<FormInputModel>): boolean {
172175
const errors: ReadonlyArray<FormInputModel> = inputs?.filter(formInputDef => {
173-
formInputDef.dirty = formInputDef.dirty || event === 'submit'
174-
validateField(formInputDef, formElements, event)
175-
return !!formInputDef.error
176-
})
176+
formInputDef.dirty = formInputDef.dirty || event === 'submit'
177+
validateField(formInputDef, formElements, event)
178+
return !!formInputDef.error
179+
})
177180
return !errors.length
178181
}

src-ts/lib/styles/_buttons.scss

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
border: solid $border;
2424
white-space: nowrap;
2525
cursor: pointer;
26-
font-family: $font-roboto;
26+
font-family: $font-roboto;
2727
font-weight: $font-weight-bold;
2828
font-size: 11px;
2929
line-height: 24px;
@@ -32,7 +32,7 @@
3232
&.primary,
3333
&.secondary {
3434

35-
&:focus {
35+
&:not(.disabled):focus {
3636
outline: $border solid $turq-140;
3737
}
3838
}
@@ -47,7 +47,7 @@
4747
padding: $border $pad-xl;
4848
font-size: 13px;
4949
}
50-
50+
5151
&.button-md {
5252
padding: 3*$border $pad-xl;
5353
font-size: 13px;
@@ -57,7 +57,7 @@
5757
padding: $pad-sm $pad-xxl;
5858
font-size: 14px;
5959
}
60-
60+
6161
&.button-xl {
6262
padding: $pad-md $pad-xxl;
6363
font-size: 16px;
@@ -85,6 +85,7 @@
8585
color: $black-60;
8686
background-color: $black-5;
8787
border-color: $black-5;
88+
cursor: default;
8889
}
8990
}
9091

@@ -105,6 +106,7 @@
105106
color: $black-60;
106107
background-color: $black-5;
107108
border-color: $black-5;
109+
cursor: default;
108110
}
109111
}
110112

@@ -125,6 +127,7 @@
125127
color: $black-60;
126128
background-color: $tc-white;
127129
border-color: $black-5;
130+
cursor: default;
128131
}
129132
}
130133

@@ -156,6 +159,7 @@
156159
color: $black-60;
157160
background-color: $tc-white;
158161
border-color: $black-5;
162+
cursor: default;
159163
}
160164

161165
svg {

src-ts/tools/work/work-detail-header/WorkFeedback/work-feedback-form.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export const workFeedbackFormDef: FormDefinition = {
55
primaryGroup: [
66
{
77
buttonStyle: 'primary',
8-
isSave: true,
8+
isSubmit: true,
99
label: 'Mark as done',
1010
size: 'xl',
1111
type: 'submit',

src-ts/tools/work/work-lib/work-provider/work-functions/work-factory/work.factory.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,6 @@ export function buildUpdateBody(workTypeConfig: WorkTypeConfig, challenge: Chall
112112
const form: IntakeForm = !!intakeForm?.value ? JSON.parse(intakeForm.value)?.form : {}
113113
form.basicInfo = formData
114114

115-
// TODO: Add the progress.currentStep to form to determine if it's in the review phase (review page)
116-
// or not. The legacy intakes use currentStep 7 for review and 5 for taking the user to the login step
117-
// as those numbers map to the routes configured for each work type (see IntakeForm.jsx for an example).
118-
// We can probably clean that up as we don't need that many routes
119-
120115
// --- Build Metadata --- //
121116
const intakeMetadata: Array<ChallengeMetadata> = [
122117
{
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { FC, useContext } from 'react'
2+
import { Outlet, Routes } from 'react-router-dom'
3+
4+
import {
5+
ContentLayout,
6+
routeContext,
7+
RouteContextData,
8+
} from '../../../../lib'
9+
10+
export const intakeFormsTitle: string = 'Work Intake Forms'
11+
12+
const IntakeForms: FC<{}> = () => {
13+
14+
const { getChildRoutes }: RouteContextData = useContext(routeContext)
15+
16+
return (
17+
<ContentLayout>
18+
<Outlet />
19+
<Routes>
20+
{getChildRoutes(intakeFormsTitle)}
21+
</Routes>
22+
</ContentLayout>
23+
)
24+
}
25+
26+
export default IntakeForms

src-ts/tools/work/work-self-service/intake-forms/bug-hunt/BugHuntIntakeForm.tsx

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { NavigateFunction, useNavigate, useParams } from 'react-router-dom'
33

44
import {
55
Form,
6+
FormAction,
67
FormDefinition,
78
formGetInputModel,
89
FormInputModel,
@@ -28,6 +29,7 @@ import { WorkIntakeFormRoutes } from '../../../work-lib/work-provider/work-funct
2829
import { WorkServicePrice } from '../../../work-service-price'
2930
import { WorkTypeBanner } from '../../../work-type-banner'
3031
import { dashboardRoute } from '../../../work.routes'
32+
import IntakeFormsBreadcrumb from '../intake-forms-breadcrumb/IntakeFormsBreadcrumb'
3133

3234
import { BugHuntFormConfig } from './bug-hunt.form.config'
3335
import styles from './BugHunt.module.scss'
@@ -44,23 +46,23 @@ const BugHuntIntakeForm: React.FC = () => {
4446
const isMobile: boolean = useCheckIsMobile()
4547
const { isLoggedIn }: ProfileContextData = useContext<ProfileContextData>(profileContext)
4648

47-
let action: string = ''
48-
BugHuntFormConfig.buttons.primaryGroup[0].onClick = () => { action = 'save' }
49-
BugHuntFormConfig.buttons.primaryGroup[1].onClick = () => { action = 'submit' }
49+
const [action, setAction]: [FormAction, Dispatch<SetStateAction<FormAction>>] = useState()
50+
51+
BugHuntFormConfig.buttons.primaryGroup[0].onClick = () => { setAction('save') }
52+
BugHuntFormConfig.buttons.primaryGroup[1].onClick = () => { setAction('submit') }
53+
if (BugHuntFormConfig.buttons.secondaryGroup) {
54+
BugHuntFormConfig.buttons.secondaryGroup[0].onClick = () => { navigate(-1) }
55+
}
5056

5157
const [challenge, setChallenge]: [Challenge | undefined, Dispatch<SetStateAction<Challenge | undefined>>] = useState()
5258
const [formDef, setFormDef]: [FormDefinition, Dispatch<SetStateAction<FormDefinition>>]
5359
= useState<FormDefinition>({ ...BugHuntFormConfig })
5460

55-
const [formValues, setFormValues]: [any, Dispatch<any>] = useState({
61+
const [formValues, setFormValues]: [any, Dispatch<any>] = useState({
5662
currentStep: 'basicInfo',
5763
[ChallengeMetadataName.packageType]: 'standard',
5864
})
5965

60-
function findMetadata(metadataName: ChallengeMetadataName): ChallengeMetadata | undefined {
61-
return challenge?.metadata?.find((item: ChallengeMetadata) => item.name === metadataName)
62-
}
63-
6466
const [selectedPackage, setSelectedPackage]: [PricePackageName, Dispatch<SetStateAction<PricePackageName>>]
6567
= useState<PricePackageName>(formValues.packageType)
6668

@@ -77,17 +79,9 @@ const BugHuntIntakeForm: React.FC = () => {
7779

7880
const intakeFormBH: ChallengeMetadata | undefined = response.metadata?.find((item: ChallengeMetadata) => item.name === ChallengeMetadataName.intakeForm)
7981
if (intakeFormBH) {
80-
const formData: Record<string, any> = JSON.parse(intakeFormBH.value)
81-
// TODO: Set the correct currentStep into challenge's form data when saving form and moving on to a new page
82-
if (formData.currentStep && formData.currentStep !== 'basicInfo') {
83-
if (!isLoggedIn) {
84-
navigate(WorkIntakeFormRoutes[WorkType.bugHunt]['loginPrompt'])
85-
} else {
86-
navigate(WorkIntakeFormRoutes[WorkType.bugHunt][formData.currentStep])
87-
}
88-
}
82+
const formData: Record<string, any> = JSON.parse(intakeFormBH.value).form.basicInfo
8983

90-
setFormValues(formData.form.basicInfo)
84+
setFormValues(formData)
9185

9286
if (formData.form.basicInfo.packageType !== selectedPackage) {
9387
setSelectedPackage(formData.form.basicInfo.packageType)
@@ -128,6 +122,11 @@ const BugHuntIntakeForm: React.FC = () => {
128122

129123
const onSave: (val: any) => Promise<void> = (val: any) => {
130124
if (!challenge) { return Promise.resolve() }
125+
if (action === 'save') {
126+
val.currentStep = 'basicInfo'
127+
} else if (action === 'submit') {
128+
val.currentStep = 'review'
129+
}
131130

132131
return workUpdateAsync(WorkType.bugHunt, challenge, val)
133132
}
@@ -151,6 +150,10 @@ const BugHuntIntakeForm: React.FC = () => {
151150

152151
return (
153152
<>
153+
<IntakeFormsBreadcrumb
154+
basicInfoRoute={WorkIntakeFormRoutes[WorkType.bugHunt]['basicInfo']}
155+
workType={workBugHuntConfig.type}
156+
/>
154157
<WorkTypeBanner
155158
title={workBugHuntConfig.title}
156159
subTitle={workBugHuntConfig.subtitle}
@@ -182,6 +185,7 @@ const BugHuntIntakeForm: React.FC = () => {
182185
onSuccess={onSaveSuccess}
183186
requestGenerator={requestGenerator}
184187
save={onSave}
188+
action={action}
185189
/>
186190
</div>
187191
</>

src-ts/tools/work/work-self-service/intake-forms/bug-hunt/bug-hunt.form.config.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const BugHuntFormConfig: FormDefinition = {
1515
},
1616
{
1717
buttonStyle: 'primary',
18+
isSubmit: true,
1819
label: 'Complete and pay',
1920
onClick: () => { },
2021
type: 'submit',
@@ -92,7 +93,7 @@ export const BugHuntFormConfig: FormDefinition = {
9293
{
9394
label: 'Features to test (optional)',
9495
name: ChallengeMetadataName.featuresToTest,
95-
placeholder: 'List the sepcific features',
96+
placeholder: 'List the specific features',
9697
type: 'textarea',
9798
},
9899
],

0 commit comments

Comments
 (0)