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

Design Review - Point of Contact #548

Merged
merged 80 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
ff3abb9
useeffect refactor wip
shindigira May 20, 2024
ec36f11
refactor(contact): useEffect is now cleaner and with typescript
shindigira May 20, 2024
ca1959a
content(contact): updated the TextIntroduction and the SectionIntrod…
shindigira May 20, 2024
61c67d9
content(contact): updated text
shindigira May 21, 2024
1aa20bb
refactor(contact link): utilizes the RegulationB in commonLinks
shindigira May 21, 2024
4a25256
refactor(getZodKeys): import updates
shindigira May 21, 2024
e2b64e2
Merge branch 'main' of github.com:cfpb/sbl-frontend into 483-design-r…
shindigira May 21, 2024
f071c8a
content(contact): updated states.json to include states abbrev
shindigira May 21, 2024
afa44a5
chore(routes - poc): commented out unused route
shindigira May 21, 2024
4aec735
enhancement(poc): updated layout
shindigira May 21, 2024
a4c4051
content(poc): states updated with 6 us territories
shindigira May 21, 2024
c65c671
TODO comment
shindigira May 21, 2024
75dbf88
conflict
shindigira May 21, 2024
cb74dae
TODO: fix 'isDirty'
shindigira May 21, 2024
0d68f3e
feat(contact): added additional text to zip code and phone number
shindigira May 21, 2024
626c085
content(contact): State or territory
shindigira May 21, 2024
d361f39
feat(contact): Added success alert if previously submitted poc was su…
shindigira May 21, 2024
a0aaf37
comment
shindigira May 21, 2024
e245365
fix: [Point of Contact] Recognize isDirty when changing State input
meissadia May 22, 2024
8e60e78
feat(contact): input error styling
shindigira May 22, 2024
88d0f6a
Merge branch '483-design-review-poc' of github.com:cfpb/sbl-frontend …
shindigira May 22, 2024
53220c7
reinstated isDirty
shindigira May 22, 2024
f19e2e1
chore(contact): disable success alert on 'clear form'
shindigira May 22, 2024
51fbf52
chore(contact):flipped order of phone and email in error header
shindigira May 22, 2024
d768bb8
Merge branch 'main' of github.com:cfpb/sbl-frontend into 483-design-r…
shindigira May 22, 2024
e30ceb7
chore(contact): errorAlertHeader becomes alertHeading
shindigira May 22, 2024
7721cf8
chore(TextInput uses): explicit showError -- affects nothing, TextInp…
shindigira May 22, 2024
78860ee
refactor(formErrorHeader): accepts an object of error header messages
shindigira May 22, 2024
8389bf0
refactor(CompleteUserProfile): Updated fieldlevel and formErrorHeader…
shindigira May 22, 2024
59a1ff4
refactor(formErrorHeader): complete your user profile no associations…
shindigira May 22, 2024
0ab8d73
removed comment
shindigira May 22, 2024
6913d1f
refactor(formErrorHeader): types of financial institution handling
shindigira May 22, 2024
db70306
chore(ufp form error headers): wip
shindigira May 22, 2024
dc8f9ee
refactor(ufp): progress
shindigira May 23, 2024
6e4805c
refactor(formErrorHeader): UpdateYourFinancialProfile form's field an…
shindigira May 23, 2024
e969e57
allow empty parent_lei top_holder_lei -- .or(z.literal(''))
shindigira May 23, 2024
a0fc4fa
refactor(formErrorHeader): moved fieldlevel errors and error header m…
shindigira May 23, 2024
f4934ec
style(contact): 10px between secondary text in the form
shindigira May 23, 2024
6957cae
Merge branch '483-design-review-poc' of github.com:cfpb/sbl-frontend …
shindigira May 23, 2024
0c45b51
style(InputEntry): 30px --> 15px between inputs
shindigira May 23, 2024
1a2a165
resolved merge conflict
shindigira May 23, 2024
6dc5a7f
filing link update
shindigira May 23, 2024
14a38ac
typos
shindigira May 23, 2024
98b27a1
Updated Update your financial profile based on figma - Second pass
shindigira May 23, 2024
1740248
updated content on ufp
shindigira May 23, 2024
686f40e
updated content on both complete your user profile forms - second pass
shindigira May 23, 2024
3c47c38
set order of errors in ufp
shindigira May 23, 2024
54c0289
updated Types of FI - second pass
shindigira May 23, 2024
0e40c1d
updated poc field errors and header errors - second pass
shindigira May 23, 2024
855eebc
updated poc -- conditional alert error heading
shindigira May 24, 2024
400caf2
Missed two typos
shindigira May 24, 2024
d40d365
added condition alert heading for types of fi
shindigira May 24, 2024
9c9695b
an LEI
shindigira May 24, 2024
9f6dca6
helper text added to poc
shindigira May 24, 2024
17eb4ab
feat(contact): states and territories now come from a fetched endpoint
shindigira May 24, 2024
e722a7d
Merge branch 'main' of github.com:cfpb/sbl-frontend into 483-design-r…
shindigira May 24, 2024
daf5ee7
Merge branch '483-design-review-poc' of github.com:cfpb/sbl-frontend …
shindigira May 24, 2024
d78235f
utilized helperText
shindigira May 24, 2024
7c6d060
updated view your financial profile content - second pass
shindigira May 24, 2024
fc3b836
one word change
shindigira May 24, 2024
5545e45
style(InputEntry): reset margin between InputEntry's to 30px
shindigira May 24, 2024
e8d7ae4
updated types of FI -- third pass
shindigira May 24, 2024
7c671f2
fix: 0.938 problem
shindigira May 24, 2024
143e495
style fix -- between state and zip
shindigira May 24, 2024
b3e5b58
enhancement(contact): if there is even just one formatting error, cha…
shindigira May 24, 2024
1a58245
ufp update
shindigira May 24, 2024
bf035c9
complete user profile more content updates
shindigira May 24, 2024
8e50e3d
one more content update
shindigira May 24, 2024
92b972a
CUPNF change
shindigira May 24, 2024
a923241
CupNFZodSchemaErrors.financialInstitutionsMin update
shindigira May 24, 2024
7852e40
hyphen
shindigira May 24, 2024
c107af1
refactor(formErrorHeader), [Point of Contact] Field-level Errors Upda…
shindigira May 24, 2024
aa83a09
Merge branch '483-design-review-poc-and-formerrorheader-refactor' of …
shindigira May 24, 2024
e9b775f
resolvedmerge conflicts
shindigira May 24, 2024
119c169
alphabetic sorting of states and terroitories
shindigira May 25, 2024
facb567
removed comment
shindigira May 25, 2024
9bd3deb
Merge branch 'main' of github.com:cfpb/sbl-frontend into 483-design-r…
shindigira May 25, 2024
4002027
remove unused comment
shindigira May 26, 2024
45d502c
chore(formErrorHeader): typescript typings
shindigira May 26, 2024
e183095
Merge branch 'main' into 483-design-review-poc
shindigira May 28, 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
3 changes: 1 addition & 2 deletions src/pages/Filing/FilingApp/FilingContact.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import PointOfContact from 'pages/PointOfContact';
import { useNavigate, useParams } from 'react-router-dom';
import { FilingSteps } from './FilingSteps';

function FilingContact(): JSX.Element {
const { lei, year } = useParams();
Expand All @@ -14,7 +13,7 @@ function FilingContact(): JSX.Element {

return (
<>
<FilingSteps />
{/* <FilingSteps /> */}
shindigira marked this conversation as resolved.
Show resolved Hide resolved
<PointOfContact onSubmit={onSubmit} />
</>
);
Expand Down
115 changes: 64 additions & 51 deletions src/pages/PointOfContact/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import FormButtonGroup from 'components/FormButtonGroup';
import FormHeaderWrapper from 'components/FormHeaderWrapper';
import FormWrapper from 'components/FormWrapper';
import InputEntry from 'components/InputEntry';
import { Link } from 'components/Link';
import SectionIntro from 'components/SectionIntro';
import { Paragraph, Select, TextIntroduction } from 'design-system-react';

import { normalKeyLogic } from 'utils/getFormErrorKeyLogic';

import { zodResolver } from '@hookform/resolvers/zod';
Expand All @@ -16,6 +16,8 @@ import FormErrorHeader from 'components/FormErrorHeader';
import FormMain from 'components/FormMain';
import { LoadingContent } from 'components/Loading';
import FilingNavButtons from 'pages/Filing/FilingApp/FilingNavButtons';
import FilingSteps from 'pages/Filing/FilingApp/FilingSteps';
import InstitutionHeading from 'pages/Filing/FilingApp/InstitutionHeading';
import {
formatPointOfContactObject,
scrollToElement,
Expand All @@ -24,9 +26,11 @@ import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router-dom';
import type { FilingType } from 'types/filingTypes';
import type { PointOfContactSchema } from 'types/formTypes';
import { pointOfContactSchema } from 'types/formTypes';
import type { ContactInfoKeys, PointOfContactSchema } from 'types/formTypes';
import { ContactInfoMap, pointOfContactSchema } from 'types/formTypes';
import { contactInfoLink } from 'utils/common';
import useFilingStatus from 'utils/useFilingStatus';
import useInstitutionDetails from 'utils/useInstitutionDetails';
import statesObject from './states.json';

const defaultValuesPOC = {
Expand Down Expand Up @@ -56,7 +60,14 @@ function PointOfContact({ onSubmit }: PointOfContactProperties): JSX.Element {
lei,
year,
);
const [isLoading, setIsLoading] = useState(false);
const {
data: institution,
isLoading: isLoadingInstitution,
isError: isErrorInstitution,
} = useInstitutionDetails(lei);

const isLoading = [isLoadingInstitution, isFilingLoading].some(Boolean);
const [isSubmitting, setIsSubmitting] = useState(false);

const {
register,
Expand All @@ -77,24 +88,14 @@ function PointOfContact({ onSubmit }: PointOfContactProperties): JSX.Element {

const contactInfo = (filing as FilingType).contact_info;

if (contactInfo?.first_name) setValue('firstName', contactInfo.first_name);
if (contactInfo?.last_name) setValue('lastName', contactInfo.last_name);
if (contactInfo?.phone_number) setValue('phone', contactInfo.phone_number);
if (contactInfo?.email) setValue('email', contactInfo.email);
if (contactInfo?.hq_address_street_1)
setValue('hq_address_street_1', contactInfo.hq_address_street_1);
if (contactInfo?.hq_address_street_2)
setValue('hq_address_street_2', contactInfo.hq_address_street_2);
if (contactInfo?.hq_address_street_3)
setValue('hq_address_street_3', contactInfo.hq_address_street_3);
if (contactInfo?.hq_address_street_4)
setValue('hq_address_street_4', contactInfo.hq_address_street_4);
if (contactInfo?.hq_address_city)
setValue('hq_address_city', contactInfo.hq_address_city);
if (contactInfo?.hq_address_state)
setValue('hq_address_state', contactInfo.hq_address_state);
if (contactInfo?.hq_address_zip)
setValue('hq_address_zip', contactInfo.hq_address_zip);
if (contactInfo) {
for (const property of Object.keys(ContactInfoMap) as ContactInfoKeys[]) {
const mappedProperty = ContactInfoMap[property];
if (typeof property === 'string' && contactInfo[property]) {
setValue(mappedProperty, contactInfo[property]);
}
}
shindigira marked this conversation as resolved.
Show resolved Hide resolved
}
shindigira marked this conversation as resolved.
Show resolved Hide resolved
}, [filing, setValue]);

const onClearform = (): void => {
Expand All @@ -116,52 +117,63 @@ function PointOfContact({ onSubmit }: PointOfContactProperties): JSX.Element {
): Promise<void> => {
event.preventDefault();
const passesValidation = await trigger();
if (passesValidation) {
// Only need to hit API if the form passes validation and the data has changed
if (passesValidation && isDirty) {
try {
// Only need to hit the API if data has changed
if (isDirty) {
setIsLoading(true);
const preFormattedData = getValues();
// 1.) Sending First Name and Last Name to the backend
const formattedUserProfileObject =
formatPointOfContactObject(preFormattedData);

await submitPointOfContact(auth, {
data: formattedUserProfileObject,
lei,
filingPeriod: year,
});
setIsSubmitting(true);
const preFormattedData = getValues();
// 1.) Sending First Name and Last Name to the backend
const formattedUserProfileObject =
formatPointOfContactObject(preFormattedData);

setIsLoading(false);
}
await submitPointOfContact(auth, {
data: formattedUserProfileObject,
lei,
filingPeriod: year,
});

if (onSubmit) onSubmit(true);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
setIsLoading(false);
if (onSubmit) onSubmit(false);
} finally {
setIsSubmitting(false);
}
shindigira marked this conversation as resolved.
Show resolved Hide resolved
} else {
scrollToElement(formErrorHeaderId);
}
};

if (isFilingLoading)
return <LoadingContent message='Loading Filing data...' />;
// TODO: Redirect the user if the filing period or lei are not valid

if (isLoading) return <LoadingContent message='Loading Filing data...' />;

return (
<div id='point-of-contact'>
<FilingSteps />
<FormWrapper>
<FormHeaderWrapper>
<div className='mb-[0.9375rem]'>
<InstitutionHeading
eyebrow
name={institution?.name}
filingPeriod={year}
/>
</div>
<TextIntroduction
heading='Provide the point of contact'
subheading='Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.'
heading='Provide point of contact'
subheading="Provide the name and business contact information of a person that the Bureau or other regulators may contact with questions about your financial institution's data submission."
description={
<Paragraph>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
enim ad minim veniam, quis nostrud exercitation.
Your financial institution&apos;s point of contact information
will not be published with your financial institution&apos;s
data and pursuant to the rule will not be available to the
general public. This information is required pursuant to{' '}
<Link target='_blank' href={contactInfoLink}>
§ 1002.109(b)(3)
</Link>
shindigira marked this conversation as resolved.
Show resolved Hide resolved
.
</Paragraph>
}
/>
Expand All @@ -172,10 +184,11 @@ function PointOfContact({ onSubmit }: PointOfContactProperties): JSX.Element {
keyLogicFunc={normalKeyLogic}
/>
<div className='mb-[1.875rem]'>
<SectionIntro heading='Provide the point of contact for your submission'>
Enter the name and business contact information of a person who may
be contacted by the Bureau or other regulators with questions about
your financial institution&apos;s submission.
<SectionIntro heading='Provide contact information for your submission'>
You are required to complete all fields with the exception of the
street address lines labeled optional. Your point of contact
information will not be saved until you provide all required
information and click &quot;Save and continue.&quot;
</SectionIntro>
</div>
{/* eslint-disable-next-line @typescript-eslint/no-misused-promises */}
Expand Down Expand Up @@ -254,7 +267,7 @@ function PointOfContact({ onSubmit }: PointOfContactProperties): JSX.Element {
onNextClick={onSubmitButtonAction}
onPreviousClick={onPreviousClick}
onClearClick={onClearform}
isLoading={isLoading}
isLoading={isSubmitting}
/>
</FormButtonGroup>
</FormMain>
Expand Down
1 change: 0 additions & 1 deletion src/types/filingTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ export interface SubmissionResponse {
validation_results: ValidationResults | null;
submission_time: Date | null;
filename: string;
total_records: number;
submitter: UserActionDTO;
accepter: UserActionDTO | null;
total_records: number;
Expand Down
18 changes: 18 additions & 0 deletions src/types/formTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,24 @@ export const pointOfContactSchema = basicInfoSchema.extend({

export type PointOfContactSchema = z.infer<typeof pointOfContactSchema>;

export const ContactInfoMap = {
first_name: 'firstName',
last_name: 'lastName',
phone_number: 'phone',
email: 'email',
hq_address_street_1: 'hq_address_street_1',
hq_address_street_2: 'hq_address_street_2',
hq_address_street_3: 'hq_address_street_3',
hq_address_street_4: 'hq_address_street_4',
hq_address_city: 'hq_address_city',
hq_address_state: 'hq_address_state',
hq_address_zip: 'hq_address_zip',
} as const;

export type ContactInfoMapType = typeof ContactInfoMap;
export type ContactInfoKeys = keyof typeof ContactInfoMap;
export type ContactInfoValues = (typeof ContactInfoMap)[ContactInfoKeys];

export type FormattedPointOfContactSchema = Omit<
PointOfContactSchema,
'firstName' | 'lastName' | 'phone'
Expand Down
2 changes: 2 additions & 0 deletions src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const fileFormatLink =
'https://www.consumerfinance.gov/data-research/small-business-lending/filing-instructions-guide/2024-guide/#2.2';
export const dataValidationLink =
'https://www.consumerfinance.gov/data-research/small-business-lending/filing-instructions-guide/2024-guide/#4';
export const contactInfoLink =
'https://www.federalregister.gov/documents/2023/05/31/2023-07230/small-business-lending-under-the-equal-credit-opportunity-act-regulation-b#p-4309';
export const sblHelpMail =
'mailto:[email protected]?subject=[BETA] Upload: Questions on File Formatting';

Expand Down
23 changes: 11 additions & 12 deletions src/utils/getZodKeys.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
import { z } from 'zod';

// get zod object keys recursively
const zodKeys = <T extends z.ZodTypeAny>(schema: T): string[] => {
// make sure schema is not null or undefined
if (schema === null || schema === undefined) return [];
export const getZodKeys = (schema: z.ZodType): string[] => {
// Adjusted: Signature now uses Zod.ZodType to eliminate null& undefined check
// check if schema is nullable or optional
if (schema instanceof z.ZodNullable || schema instanceof z.ZodOptional)
return zodKeys(schema.unwrap());
if (schema instanceof z.ZodNullable || schema instanceof z.ZodOptional) {
return getZodKeys(schema.unwrap());
}
// check if schema is an array
if (schema instanceof z.ZodArray) return zodKeys(schema.element);
if (schema instanceof z.ZodArray) {
return getZodKeys(schema.element);
}
// check if schema is an object
if (schema instanceof z.ZodObject) {
// get key/value pairs from schema
const entries = Object.entries(schema.shape);
const entries = Object.entries<Zod.ZodType>(schema.shape); // Adjusted: Uses Zod.ZodType as generic to remove instanceof check. Since .shape returns ZodRawShape which has Zod.ZodType as type for each key.
// loop through key/value pairs
return entries.flatMap(([key, value]) => {
// get nested keys
const nested =
value instanceof z.ZodType
? zodKeys(value).map(subKey => `${key}.${subKey}`)
: [];
const nested = getZodKeys(value).map(subKey => `${key}.${subKey}`);
// return nested keys
return nested.length > 0 ? nested : key;
});
Expand All @@ -28,4 +27,4 @@ const zodKeys = <T extends z.ZodTypeAny>(schema: T): string[] => {
return [];
};

export default zodKeys;
export default getZodKeys;