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

[Task] [User Profile] useRef in Step1FormErrorHeader #145

Merged
merged 25 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bb72c45
feat: utilized useRef in Step1FormErrorHeader
shindigira Jan 5, 2024
ce62c0d
fix: lint errors
shindigira Jan 5, 2024
723a628
Merge branch 'main' of github.com:cfpb/sbl-frontend into 102-useref-u…
shindigira Jan 5, 2024
08e9818
chore: moved function to profileformutils
shindigira Jan 8, 2024
ef2cf34
feat: added back scroll animation to step1formerrorheader
shindigira Jan 8, 2024
3755c93
chore: remove unused useref
shindigira Jan 8, 2024
792c599
Merge branch 'main' of github.com:cfpb/sbl-frontend into 102-useref-u…
shindigira Jan 8, 2024
6da8228
fix: removed unused var
shindigira Jan 9, 2024
e2707e4
refactor: added type to submitprofile
shindigira Jan 9, 2024
14cc083
chore: pulled in 85 and fixed merge conflicts
shindigira Jan 10, 2024
3c66a20
fix: resolved merge conflicts
shindigira Jan 11, 2024
c879e4c
fix: resolved conflicts 2
shindigira Jan 11, 2024
8e5a831
revert: reverted to document.querySelector use
shindigira Jan 11, 2024
4235401
fix: resolved merge conflicts
shindigira Jan 11, 2024
2095f1e
Merge branch '85-dsr-userprofile' of github.com:cfpb/sbl-frontend int…
shindigira Jan 11, 2024
e7d551a
Merge branch '85-dsr-userprofile' of github.com:cfpb/sbl-frontend int…
shindigira Jan 11, 2024
27b3f9e
Merge branch '85-dsr-userprofile' of github.com:cfpb/sbl-frontend int…
shindigira Jan 11, 2024
1d95da5
style: merge conflicts fix
shindigira Jan 11, 2024
e9bbebc
rebased off of 85
shindigira Jan 11, 2024
b9b7f02
Merge branch '85-dsr-userprofile' of github.com:cfpb/sbl-frontend int…
shindigira Jan 11, 2024
e6c9095
fix: resolved merge conflicts
shindigira Jan 11, 2024
3e996b2
fix: resolved merge conflicts
shindigira Jan 11, 2024
a7dd6f8
style: fix gray/red border
shindigira Jan 11, 2024
9d66483
style: remove periods from error header text
shindigira Jan 11, 2024
d47bfe4
chore: removed class attribute
shindigira Jan 11, 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/api/submitUserProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { SblAuthProperties } from 'api/useSblAuth';
import { BASE_URL } from 'api/common';

interface UserProfileObject {
first_name: string;
Expand All @@ -10,7 +9,7 @@ interface UserProfileObject {
export const submitUserProfile = async (
auth: SblAuthProperties,
userProfileObject: UserProfileObject,
): Promise<any> => {
): object => {
const response = await fetch(`/v1/admin/me/`, {
headers: {
Authorization: `Bearer ${auth.user?.access_token}`,
Expand Down
6 changes: 3 additions & 3 deletions src/components/ProfileFormWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ interface Properties {

function ProfileFormWrapper({children}: Properties): JSX.Element {
return (
<div className="ml-5 mr-5 mt-[45px]">
<div className="max-w-[1200px] mx-auto mb-12">
<div className="max-w-[770px] mx-auto">
<div className="ml-5 mr-5 mt-[2.813rem]">
<div className="max-w-[75rem] mx-auto mb-12">
<div className="max-w-[48.125rem] mx-auto">
{ children }
</div>
</div>
Expand Down
17 changes: 16 additions & 1 deletion src/pages/ProfileForm/ProfileFormUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { ValidationSchema, FormattedUserProfileObjectType } from './types';
import type {
InstitutionDetailsApiType,
InstitutionDetailsApiCheckedType,
ValidationSchema,
FormattedUserProfileObjectType
} from 'pages/ProfileForm/types';

export const formatUserProfileObject = (
userProfileObject: ValidationSchema,
Expand All @@ -7,3 +12,13 @@ export const formatUserProfileObject = (
last_name: userProfileObject.lastName,
leis: userProfileObject.financialInstitutions.map(object => object.lei),
});

export const formatDataCheckedState = (
fiDataInput: InstitutionDetailsApiType[] = [],
): InstitutionDetailsApiCheckedType[] =>
fiDataInput.map(object => ({ ...object, checked: false }));

export default {
formatUserProfileObject,
formatDataCheckedState
}
80 changes: 48 additions & 32 deletions src/pages/ProfileForm/Step1Form/Step1Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import useSblAuth from 'api/useSblAuth';
import { useEffect, useState } from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form';
import { Element } from 'react-scroll';
import { Element, scroller } from 'react-scroll';
import { useNavigate } from 'react-router-dom';

import AssociatedFinancialInstitutions from './AssociatedFinancialInstitutions';
Expand All @@ -14,7 +14,7 @@ import FormParagraph from 'components/FormParagraph';
import FieldGroup from 'components/FieldGroup';
import InputErrorMessage from 'components/InputErrorMessage';

import { Button, Link } from 'design-system-react';
import { Button, Link, Heading } from 'design-system-react';

import { fiOptions, fiData } from 'pages/ProfileForm/ProfileForm.data';
import type {
Expand All @@ -31,27 +31,33 @@ import InputEntry from './InputEntry';
import Step1FormErrorHeader from './Step1FormErrorHeader';
import Step1FormHeader from './Step1FormHeader';

import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
import useProfileForm from 'store/useProfileForm';
import Step1FormDropdownContainer from './Step1FormDropdownContainer';

import fetchInstitutions from 'api/fetchInstitutions';
import submitUserProfile from 'api/submitUserProfile';
import { formatUserProfileObject } from 'pages/ProfileForm/ProfileFormUtils';
import {
formatUserProfileObject,
formatDataCheckedState,
} from 'pages/ProfileForm/ProfileFormUtils';

function Step1Form(): JSX.Element {
/* Initial- Fetch all institutions */
const auth = useSblAuth();

const email = auth.user?.profile.email;
// eslint-disable-next-line unicorn/prefer-string-slice
const emailDomain = email?.substring(email.lastIndexOf('@')+1);
const { isLoading, isError, data: afData} = useQuery({
queryKey: [`fetch-institutions-${emailDomain}`, emailDomain],
const emailDomain = email?.substring(email.lastIndexOf('@') + 1);
const {
isLoading,
isError,
data: afData,
} = useQuery({
queryKey: [`fetch-institutions-${emailDomain}`, emailDomain],
queryFn: async () => fetchInstitutions(auth, emailDomain),
enabled: !!emailDomain,
});


const defaultValues: ValidationSchema = {
firstName: '',
Expand Down Expand Up @@ -79,10 +85,6 @@ function Step1Form(): JSX.Element {

/* Selected State - Start */
// Associated Financial Institutions state
const formatDataCheckedState = (
fiDataInput: InstitutionDetailsApiType[] = [],
): InstitutionDetailsApiCheckedType[] =>
fiDataInput.map(object => ({ ...object, checked: false }));
const [checkedListState, setCheckedListState] = useState<
InstitutionDetailsApiCheckedType[]
>([]);
Expand All @@ -98,20 +100,19 @@ function Step1Form(): JSX.Element {

// Formatting: Checkmarking either the Associated Financial Institutions or the Dropdown Financial Institutions, adds to the react-hook-form object
/* Format - Start */

const getFinancialInstitutionsFormData = (
checkedListState: InstitutionDetailsApiCheckedType[],
checkedListStateArray: InstitutionDetailsApiCheckedType[],
): InstitutionDetailsApiType[] => {
const newFinancialInstitutions: InstitutionDetailsApiType[] = [];

checkedListState.forEach((object: InstitutionDetailsApiCheckedType) => {
for (const object of checkedListStateArray) {
if (object.checked) {
const foundObject: InstitutionDetailsApiType = afData?.find(
institutionsObject => object.lei === institutionsObject.lei,
);
newFinancialInstitutions.push(foundObject);
}
});
}

// TODO: Added multiselected to list of selected institutions

Expand All @@ -131,11 +132,30 @@ function Step1Form(): JSX.Element {
}, [checkedListState, selectedFI]);
/* Format - End */

// Post Submission
const setStep = useProfileForm(state => state.setStep);
const setProfileData = useProfileForm(state => state.setProfileData);
const navigate = useNavigate();

const enableMultiselect = useProfileForm(state => state.enableMultiselect);
const isSalesforce = useProfileForm(state => state.isSalesforce);

// 'Clear Form' function
function clearForm(): void {
setValue('firstName', '');
setValue('lastName', '');
setSelectedFI([]);
setCheckedListState([]);
window.scrollTo({ top: 0, behavior: 'smooth' });
}

// Used for smooth scrolling to the Step1FormErrorHeader upon error
const scrollToErrorForm = (): void => {
scroller.scrollTo('step1FormErrorHeader', {
duration: 375,
smooth: true,
offset: -25, // Scrolls to element 25 pixels above the element
});
};

// Post Submission
const onSubmitButtonAction = async (): Promise<void> => {
// TODO: Handle error UX on submission failure or timeout
const userProfileObject = getValues();
Expand All @@ -152,29 +172,25 @@ function Step1Form(): JSX.Element {
// https://github.com/cfpb/sbl-frontend/issues/135
await auth.signinSilent();
window.location.href = '/landing';
// navigate('/landing')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// navigate('/landing')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the issue is solved, navigating should done this way. Using window.location.href causes a hard refresh.
I'm keeping the comment here since it will eventually be uncommented.

} else {
// on errors scroll to Step1FormErrorHeader
scrollToErrorForm();
}
};

const navigate = useNavigate();

// 'Clear Form' function
function clearForm(): void {
setValue('firstName', '');
setValue('lastName', '');
setSelectedFI([]);
setCheckedListState(initialDataCheckedState);
window.scrollTo({ top: 0, behavior: 'smooth' });
}

// Based on useQuery states
if (!auth.user?.access_token) return <>Login first!</>;
if (isLoading) return <>Loading Institutions!</>;
if (isError) return <>Error on loading institutions!</>;

return (
<div id='step1form'>
<Step1FormHeader />
<Step1FormErrorHeader errors={formErrors} />
<h3>Provide your identifying information</h3>
<Element name='step1FormErrorHeader' id='step1FormErrorHeader'>
<Step1FormErrorHeader errors={formErrors} />
</Element>
<Heading type='3'>Provide your identifying information</Heading>
<FormParagraph>
Type your first name and last name in the fields below. Your email
address is automatically populated from <Link href='#'>Login.gov</Link>.
Expand Down
98 changes: 56 additions & 42 deletions src/pages/ProfileForm/Step1Form/Step1FormErrorHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,50 @@
import { Alert } from 'design-system-react';
import { Link } from 'react-scroll';
import { useRef, forwardRef } from 'react';

import { FormFieldsHeaderError as formFieldsHeaderError } from 'pages/ProfileForm/types';

interface LinkProperties {
id: string
}

function Step1FormErrorHeaderLink({id}: LinkProperties): JSX.Element {
const fieldReference = useRef(document.querySelector(`#${id}`) as HTMLElement | undefined);

const focusKeyItem = (): void => {
if (fieldReference.current) {
fieldReference.current.focus();
}
};

const onHandleFocus = (): void => {
focusKeyItem();
};

const onHandleKeyPress = (event: React.KeyboardEvent<HTMLAnchorElement>): void => {
if (event.key === 'Enter' || event.key === " ") {
focusKeyItem();
}
};

return (
<span className="flex mb-2" key={`${id}-mb-2`}>
<Link
className='cursor-default'
to={id}
smooth
duration={300}
offset={-100}
onClick={onHandleFocus}
onKeyPress={onHandleKeyPress}
tabIndex={0}
>
{formFieldsHeaderError[id as keyof typeof formFieldsHeaderError]}
</Link>
</span>
);
}

interface Step1FormErrorHeaderProperties {
errors: object
}
Expand All @@ -11,57 +53,29 @@ interface Step1FormErrorHeaderProperties {
*
* @returns List of Schema Errors - for Step1Form
*/
function Step1FormErrorHeader({ errors }: Step1FormErrorHeaderProperties): JSX.Element {
const Step1FormErrorHeader = forwardRef<HTMLDivElement, Step1FormErrorHeaderProperties>(
(
{ errors }, reference
) => {


// formErrors && Object.keys(formErrors).length > 0
if (!errors || Object.keys(errors).length === 0) return null;

return (
<div className="w-full mb-[30px]">
<div className="w-full mb-[30px]" ref={reference}>
<Alert
message="There was a problem completing your profile"
status="error"
>
{Object.keys(errors).filter(k => k !== "fiData").map((key: string): JSX.Element => {

const focusKeyItem = (): void => {
// TODO: Refactor with useRef - https://github.com/cfpb/sbl-frontend/issues/102
const element = document.querySelector(`#${key}`) as HTMLElement | undefined;
if (element) {
element.focus();
}
};

const onHandleFocus = (): void => {
focusKeyItem();
};

const onHandleKeyPress = (event: React.KeyboardEvent<HTMLAnchorElement>): void => {
if (event.key === 'Enter' || event.key === " ") {
focusKeyItem();
}
};

return (
<span className="flex mb-2" key={key}>
<Link
className='cursor-default'
to={key}
smooth
duration={300}
offset={-100}
onClick={onHandleFocus}
onKeyPress={onHandleKeyPress}
tabIndex={0}
>
{formFieldsHeaderError[key as keyof typeof formFieldsHeaderError]}
</Link>
</span>
)
})}
{
Object.keys(errors).filter(k => k !== "fiData").map((id: string): JSX.Element => <Step1FormErrorHeaderLink key={id} id={id} />)
}
</Alert>
</div>
)
}
)
}
)

export default Step1FormErrorHeader;

export default Step1FormErrorHeader;
10 changes: 6 additions & 4 deletions src/store/useProfileForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { immer } from 'zustand/middleware/immer';
interface State {
step: number,
profileData: ValidationSchema,
selectedScenario: Scenario
selectedScenario: Scenario,
enableMultiselect: boolean,
isSalesforce: boolean
}

interface Actions {
Expand All @@ -27,15 +29,15 @@ const useProfileForm = create(
// Step 2 toggles
selectedScenario: Scenario.Error1,
// setters
setStep: (by) =>
setStep: (by): void =>
set((state: State) => {
state.step = by
}),
setProfileData: (vObject) =>
setProfileData: (vObject): void =>
set((state: State) => {
state.profileData = vObject
}),
setSelectedScenario: (scenario) =>
setSelectedScenario: (scenario): void =>
set((state: State) => {
state.selectedScenario = scenario
}),
Expand Down