Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(Review third) Update FI profile - mail api integration #283

Merged
merged 48 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
48949ca
fix: [CrumbTrail] Allow conditional entries
meissadia Feb 8, 2024
240aff4
fix: [UpdateInstitutionProfile]
meissadia Feb 8, 2024
adf4a18
fix: [UpdateInstitutionProfile]
meissadia Feb 8, 2024
d0a4a62
feat: [UpdateFinancialProfile] Display FinancialInstitutionDetails
meissadia Feb 22, 2024
661ae59
feat: [UpdateFinancialProfile] Display FinancialInstitutionDetails an…
meissadia Feb 22, 2024
3b3233b
feat: [UpdateFinancialProfile] Display UpdateIdentifyingInformation a…
meissadia Feb 9, 2024
1d4415f
feat: [InputEntry] Support display of (optional) label
meissadia Feb 23, 2024
c527d56
fix: Lint error
meissadia Feb 23, 2024
de66307
feat: [UpdateFinancialProfile] Display AffiliateInformation and inclu…
meissadia Feb 23, 2024
b926c51
feat: [LabelOptional] Extract to a reusable component
meissadia Feb 23, 2024
190eb16
feat: [UpdateFinancialProfile] Display AffiliateInformation and inclu…
meissadia Feb 23, 2024
3373a31
fix: [UpdateAffiliateInformation] Display initial checkbox state and …
meissadia Feb 23, 2024
6b5187e
feat: [UpdateFinancialProfile] Simulated success upon form submission
meissadia Feb 23, 2024
7544f44
Merge branch 'main' into 222-update-fi-profile-phase1
meissadia Feb 26, 2024
33c0a9b
Merge branch 'main' into 222-update-fi-profile-phase1
meissadia Feb 27, 2024
c20b8c6
fix: [UpdateAffiliateInformation] Extract UFPForm in prep for generat…
meissadia Feb 27, 2024
eb770a8
fix: [UpdateAffiliateInformation] Change filename case
meissadia Feb 27, 2024
3236824
fix: removed double reference
shindigira Feb 27, 2024
4753436
Update src/components/LabelOptional.tsx
meissadia Feb 27, 2024
6508285
[UpdateInstitutionProfile]
meissadia Feb 28, 2024
8588f4b
[UpdateInstitutionProfile] Update sbl_institution_types type
meissadia Feb 29, 2024
e723746
[UpdateInstitutionProfile] Code cleanup in buildUfpDefaults()
meissadia Feb 29, 2024
09ce26b
[UpdateInstitutionProfile] Format form data for email submission
meissadia Feb 29, 2024
7e571e4
[UpdateInstitutionProfile] Trigger email upon form submission
meissadia Feb 29, 2024
f46d42b
[UpdateInstitutionProfile] Remove "Simulated" label in submission sum…
meissadia Feb 29, 2024
98f3ea0
Merge branch 'main' into 222-update-fi-profile-phase1
meissadia Feb 29, 2024
16af4c5
[UpdateInstitutionProfile] Code cleanup
meissadia Feb 29, 2024
8bf0296
Merge branch 'main' into 222-update-fi-profile-phase2-mail-api-integr…
meissadia Feb 29, 2024
8cb40cf
refactor(InputEntry): InputEntry's label prop only expects string
shindigira Mar 1, 2024
64b66c4
[Update FI] New TODO - Navigate based on validation success
meissadia Mar 4, 2024
6b802db
[Update FI] Add placeholder for InputEntry prop errorMessage
meissadia Mar 4, 2024
e1975b3
Merge branch '222-update-fi-profile-phase1' of https://github.com/cfp…
meissadia Mar 4, 2024
926e154
[FormSectionWrapper] Extract to shared, reusable component
meissadia Mar 4, 2024
8b948fd
Merge branch 'main' into 222-update-fi-profile-phase3-beta
meissadia Mar 4, 2024
47e3df6
Merge branch '222-update-fi-profile-phase1' into 222-update-fi-profil…
meissadia Mar 4, 2024
b4527d0
Merge branch '222-update-fi-profile-phase3-beta' into 222-update-fi-p…
meissadia Mar 4, 2024
b0e896f
[Institution profiles] Update types to be more flexible
meissadia Mar 5, 2024
0c16504
[Utils] Extract some reusable functions
meissadia Mar 5, 2024
1d124dd
Code cleanup
meissadia Mar 5, 2024
e9c231d
collectChangedData - Simplify the way we generate data to be sent via…
meissadia Mar 5, 2024
f35a17f
[Update institution]
meissadia Mar 5, 2024
6a92ed9
[Update institution] Update crumbtrails
meissadia Mar 7, 2024
c7e5fe4
-Remove [Simulated] from mail
meissadia Mar 13, 2024
17defcc
Merge branch 'main' into 222-update-fi-profile-phase2-mail-api-integr…
meissadia Mar 14, 2024
d849e37
[Fix] Relocation of InstitutionDetailsApiType
meissadia Mar 14, 2024
7b11d7d
Extract AddressStreet2 as a component
meissadia Mar 14, 2024
46cadc0
Merge branch 'main' of github.com:cfpb/sbl-frontend into 222-update-f…
shindigira Mar 15, 2024
3a6fcfd
Merge branch 'main' into 222-update-fi-profile-phase2-mail-api-integr…
meissadia Mar 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 41 additions & 21 deletions src/api/requests/submitUpdateFinancialProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,57 @@ import { request } from 'api/axiosService';
import type { CaseType } from 'api/common';
import { caseTypes } from 'api/common';
import type { SblAuthProperties } from 'api/useSblAuth';
import type { UFPSchema } from 'pages/Filing/UpdateFinancialProfile/types';

// Used to remove 'checkboxes' property
function omit(key: string, object: UFPSchema): Record<string, string> {
// @ts-expect-error intentional key omission
const { [key]: omitted, ...rest } = object;
return rest;
}

// Pulls 'checkboxes' property out to keep the object flat, and then reinserts every checkbox property at first depth
const formatFinancialProfileObject = (
object: UFPSchema,
): Record<string, string> => {
const solution = omit('checkboxes', object);
for (const key of Object.keys(object.checkboxes)) {
solution[key] = String(object.checkboxes[key]);
import { checkboxOptions } from 'pages/Filing/UpdateFinancialProfile/types';
import type { InstitutionDetailsApiType } from 'types/formTypes';
import { One } from 'utils/constants';

export const collectChangedData = (
formData: InstitutionDetailsApiType,
changedFields: Record<string, boolean | undefined>,
): InstitutionDetailsApiType => {
const result: InstitutionDetailsApiType = {};

// Include only fields which have been identified as "changed"
for (const key of Object.keys(changedFields)) {
result[key] = formData[key] as string;
}

// Institution types are not registered as "changed" by react-hook-form (because they're in an array?), so we have to manually process them.
if (
formData.sbl_institution_types &&
typeof formData.sbl_institution_types === 'object'
) {
const sblInstitutionTypes = [];
for (const key of formData.sbl_institution_types.keys()) {
if (formData.sbl_institution_types[key]) {
const indexToTypeArray = Number(key) - One;
sblInstitutionTypes.push(checkboxOptions[indexToTypeArray].label);
}
}

result.sbl_institution_types = sblInstitutionTypes.join(', ');

// TODO: Okay to merge 'Other' into this listing?
if (sblInstitutionTypes.includes('Other'))
result.sbl_institution_types += ` (${formData.sbl_institution_types_other})`;
}
return solution;

// TODO: additional_details is not registering as "changed" (due to ref forwarding issue?), need to manually process them.
if ((formData.additional_details ?? '').length > 0)
result.additional_details = formData.additional_details;

return result;
};

const submitUpdateFinancialProfile = async (
auth: SblAuthProperties,
financialProfileObject: UFPSchema,
financialProfileObject: Record<string, string>,
): Promise<null> => {
return request<null>({
url: `/send`,
method: 'post',
// ex: 'userName=test%40gmail.com&password=Password%21&grant_type=password'
body: new URLSearchParams(
formatFinancialProfileObject(financialProfileObject),
),
body: new URLSearchParams(financialProfileObject),
headers: {
Authorization: `Bearer ${auth.user?.access_token}`,
'Content-Type': 'application/x-www-form-urlencoded',
Expand Down
89 changes: 42 additions & 47 deletions src/pages/Filing/UpdateFinancialProfile/UfpForm.tsx
Original file line number Diff line number Diff line change
@@ -1,107 +1,100 @@
import { zodResolver } from '@hookform/resolvers/zod';
import submitUpdateFinancialProfile, {
collectChangedData,
} from 'api/requests/submitUpdateFinancialProfile';
import useSblAuth from 'api/useSblAuth';
import CrumbTrail from 'components/CrumbTrail';
import FormButtonGroup from 'components/FormButtonGroup';
import FormErrorHeader from 'components/FormErrorHeader';
import FormHeaderWrapper from 'components/FormHeaderWrapper';
import FormWrapper from 'components/FormWrapper';
import { Button, Link, TextIntroduction } from 'design-system-react';
import type { JSXElement } from 'design-system-react/dist/types/jsxElement';
import type { UFPSchema } from 'pages/Filing/UpdateFinancialProfile/types';
import { ufpSchema } from 'pages/Filing/UpdateFinancialProfile/types';
import { scenarios } from 'pages/Summary/Summary.data';
import { useState } from 'react';
import { useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { Navigate, useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import type { InstitutionDetailsApiType } from 'types/formTypes';
import { institutionDetailsApiTypeSchema } from 'types/formTypes';
import { Five } from 'utils/constants';
import getIsRoutingEnabled from 'utils/getIsRoutingEnabled';
import AdditionalDetails from './AdditionalDetails';
import FinancialInstitutionDetailsForm from './FinancialInstitutionDetailsForm';
import UpdateAffiliateInformation from './UpdateAffiliateInformation';
import UpdateIdentifyingInformation from './UpdateIdentifyingInformation';
import buildUfpDefaults from './buildUfpDefaults';
import buildProfileFormDefaults from './buildProfileFormDefaults';

export default function UFPForm({
data,
}: {
data: InstitutionDetailsApiType;
}): JSXElement {
const [submitted, setSubmitted] = useState(false);
const { lei } = useParams();

const defaultValues = buildUfpDefaults(data);
const auth = useSblAuth();
billhimmelsbach marked this conversation as resolved.
Show resolved Hide resolved
const isRoutingEnabled = getIsRoutingEnabled();
const navigate = useNavigate();

const defaultValues = useMemo(() => buildProfileFormDefaults(data), [data]);

const {
register,
// trigger,
control,
setValue,
trigger,
getValues,
register,
reset,
setValue,
formState: { errors: formErrors, dirtyFields },
} = useForm<UFPSchema>({
resolver: zodResolver(ufpSchema),
} = useForm<InstitutionDetailsApiType>({
resolver: zodResolver(institutionDetailsApiTypeSchema),
defaultValues,
});

// TODO: Render this based on the actual API call result
// TODO: No need to track "submitted" state once we implement validations
// https://github.com/cfpb/sbl-frontend/pull/276/files#r1509023108
if (isRoutingEnabled && submitted) {
return (
<Navigate
to='/summary'
state={{ scenario: scenarios.SuccessInstitutionProfileUpdate }}
/>
);
}

// Used for error scrolling
const formErrorHeaderId = 'UFPFormErrorHeader';

// NOTE: This function is used for submitting the multipart/formData
const onSubmitButtonAction = async (): Promise<void> => {
const passesValidation = await trigger();
// const passesValidation = await trigger();
// TODO: Will be used for debugging after clicking 'Submit'
// eslint-disable-next-line no-console
console.log('passes validation?', passesValidation);
// console.log('passes validation?', passesValidation);
// if (passesValidation) {
const preFormattedData = getValues();
// TODO: Will be used for debugging after clicking 'Submit'
// eslint-disable-next-line no-console
console.log(
'data to be submitted (before format):',
JSON.stringify(preFormattedData, null, Five),
);

// TODO: Send data in human readable format
// POST formData
// const response = await submitUpdateFinancialProfile(
// auth,
// preFormattedData,
// )
try {
const formData = getValues();
const postableData = collectChangedData(formData, dirtyFields);
billhimmelsbach marked this conversation as resolved.
Show resolved Hide resolved
postableData.Note =
'This data reflects the institution data that has been changed';
// eslint-disable-next-line no-console
console.log(
'data being submitted:',
JSON.stringify(postableData, null, Five),
);
await submitUpdateFinancialProfile(auth, postableData);
if (isRoutingEnabled)
navigate('/summary', {
state: { scenario: scenarios.SuccessInstitutionProfileUpdate },
});
} catch (error) {
// eslint-disable-next-line no-console
console.log('Error submitting UFP', error);
}
// }
setSubmitted(true);
};

// Reset form data to the defaultValues
const onClearform = (): void => reset();

// TODO: Will be used for debugging errors after clicking 'Submit'
// eslint-disable-next-line no-console
console.log('formErrors:', formErrors);

// TODO: Use dirtyFields to determine which data to send to SBL Help
// TODO: Nested fields (sbl_institution_types) do not register as dirty when content changes, will need to always check those values
console.log('dirtyFields:', dirtyFields);
// console.log('formErrors:', formErrors);

return (
<FormWrapper>
<div id='update-financial-profile'>
<FormHeaderWrapper>
<CrumbTrail>
<Link href='/landing' key='home'>
<Link isRouterLink href='/landing' key='home'>
Platform home
</Link>
{lei ? (
Expand Down Expand Up @@ -144,6 +137,8 @@ export default function UFPForm({
aria-label='Submit User Profile'
size='default'
type='submit'
// TODO: Allow form submission without changed data?
// disabled={!(hasChanges || Object.keys(dirtyFields).length > 0)}
/>
<Button
label='Clear form'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ import { FormSectionWrapper } from '../../../components/FormSectionWrapper';
import InputEntry from '../../../components/InputEntry';
import { DisplayField } from '../ViewInstitutionProfile/DisplayField';
import type { CheckboxOption } from './types';
import { checkboxOptions, sblInstitutionTypeMap } from './types';
import { checkboxOptions } from './types';

const elements = {
taxID: 'tax_id',
rssdID: 'rssd_id',
};

const SLB_INSTITUTION_TYPE_OTHER = '13';

function FieldFederalPrudentialRegulator({
data,
register,
Expand Down Expand Up @@ -72,7 +74,7 @@ function UpdateIdentifyingInformation({
formErrors: string[];
}): JSXElement {
const typeOtherData = data.sbl_institution_types.find(item => {
return item.sbl_type.id === sblInstitutionTypeMap.other;
return item.sbl_type.id === SLB_INSTITUTION_TYPE_OTHER;
});

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { InstitutionDetailsApiType } from 'types/formTypes';
import { buildEmailDomainString } from 'utils/formatting';

// Map the Institutions API data to an easily trackable format for react-hook-form
const buildProfileFormDefaults = (
data: InstitutionDetailsApiType,
): InstitutionDetailsApiType => {
const formDefaults: InstitutionDetailsApiType = structuredClone(data);
formDefaults.domains = buildEmailDomainString(data.domains);
formDefaults.additional_details = ''; // Only part of outgoing data

// Building an easier format to track checkboxes via react-hook-form
formDefaults.sbl_institution_types = [];
if (data.sbl_institution_types) {
for (const currentType of data.sbl_institution_types) {
if (typeof currentType === 'object') {
const { details, sbl_type: sblType } = currentType;
const { id: currentId } = sblType;

formDefaults.sbl_institution_types[Number(currentId)] = true;

// Other's details
if (currentId === '13')
formDefaults.sbl_institution_types_other = details;
}
}
}

return formDefaults;
};

export default buildProfileFormDefaults;
Loading
Loading