Skip to content

Commit

Permalink
[User Profile] - Layout View - Initial User Profile Form, Frontend Va…
Browse files Browse the repository at this point in the history
…lidation Schema (#27)

* feature: Profile Form -- Start
Changes:
- [feat] Created a link to the Profile Form
- [feat] Created a route to the Profile Form
- [feat] Started a Profile Form Component

* feat: Profile form

Changes:
- [chore] Added `react-hook-form`, `zod` and `@hookform/resolvers`
- [style] Started initial form look

* feat: zod validation update

Changes:
- [chore] form update with available fields

* feat: User Profile - Step 1

Changes:
- [chore] Started a `store` folder for hooks
- [style] Utilized the CFPB style for the User Profile form buttons
- [feat] First Name and Last Name implemented on the User Profile

* chore: Auto-populate email in User Profile with logged-in user email from useSblAuth hook

* feat: Integrated Zustand/Immer, Step Forms

Changes:
- [feat]: Added `zustand` and `immer` for state management
- [chore]: Renamed the initial User Profile form as a Step1Form
- [chore]: Utilized User Profile state management via Zustand

* chore: Added 'login.gov' notice to email field

* chore: red border on missing first and last name fields

* feat: added mock associated financial data for the validation schema

* chore: InputEntry component

Changes:

- [chore]: Created the `InputEntry` component
- [chore]: Created the `ProfileForm.data.ts` to place data and mock data
- [feat]: Added `react-select` and `@types/react-select` packages

* chore: added  and  yarn cache items

* fix: added error border styling for the associated financial institution(s) field

* feat: Initial User Profile form complete

Changes:
- [feat] Included all the initial validation schema fields
- [feat] Included error messages for associated financial data
- [chore] Included the `svgr` plugin to be able to import svg files

* chore: set a link - submit a technical question

* chore: added default margin for mobile responsiveness

* fix: removed dead code

* chore: fixed form padding

* chore: Tailwind colors, Error SVG correction

Changes:
- [chore] added error and disabled color class in the tailwind config
- [chore] added the correct error icon

* chore: added additional notice regarding LEI to the User Profile form

* chore: re-added @cfpb/cfpb-design-system

* fix: removed `console.log`

Co-authored-by: Meis <meissadia@gmail.com>

* Update src/pages/ProfileForm/Step1Form.tsx

- Removed `console.log`

Co-authored-by: Meis <meissadia@gmail.com>

* Update src/pages/ProfileForm/Step1Form.tsx

Removed duplicate button

Co-authored-by: Meis <meissadia@gmail.com>

* Update src/pages/ProfileForm/Step1FormHeader.tsx

Removed duplicate paragraph

Co-authored-by: Meis <meissadia@gmail.com>

* fix: fixed missing char

---------

Co-authored-by: Meis <meissadia@gmail.com>
  • Loading branch information
shindigira and meissadia authored Aug 24, 2023
1 parent 835aaea commit 9e33de0
Show file tree
Hide file tree
Showing 62 changed files with 1,554 additions and 339 deletions.
1,002 changes: 812 additions & 190 deletions .pnp.cjs

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
14 changes: 11 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,27 @@
"validate": "run-p lint test:ci test:e2e:headless"
},
"dependencies": {
"@cfpb/cfpb-design-system": "^0.25.0",
"@cfpb/cfpb-design-system": "^0.29.0",
"@hookform/resolvers": "^3.2.0",
"@tanstack/react-query": "4.29.7",
"@tanstack/react-table": "^8.9.1",
"@trussworks/react-uswds": "^4.2.1",
"@types/react-select": "^5.0.1",
"design-system-react": "cfpb/design-system-react",
"immer": "^10.0.2",
"keycloak-js": "^21.1.1",
"less": "^4.1.3",
"oidc-client-ts": "^2.2.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.45.4",
"react-keycloak-js": "^1.0.3",
"react-oidc-context": "^2.2.2",
"react-router-dom": "6.11.1"
"react-router-dom": "6.11.1",
"react-select": "^5.7.4",
"vite-plugin-svgr": "^3.2.0",
"zod": "^3.22.0",
"zustand": "^4.4.1"
},
"devDependencies": {
"@nabla/vite-plugin-eslint": "1.5.0",
Expand All @@ -50,7 +58,7 @@
"@testing-library/user-event": "14.4.3",
"@types/css-mediaquery": "0.1.1",
"@types/node": "^20.4.5",
"@types/react": "^18.2.17",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@types/react-router-dom": "5.3.3",
"@types/testing-library__jest-dom": "5.14.5",
Expand Down
17 changes: 13 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { Button, FooterCfGov, Link, PageHeader } from 'design-system-react';
import 'design-system-react/style.css';
import FilingApp from 'pages/Filing/FilingApp';
import FilingHome from 'pages/Filing/FilingHome';
import type { ReactElement } from 'react';
import ProfileForm from 'pages/ProfileForm';
import type { ReactElement, ReactNode } from 'react';
import { Suspense } from 'react';
import {
BrowserRouter,
Expand Down Expand Up @@ -49,7 +50,8 @@ function NavItem({ href, label }: NavItemProperties): JSX.Element {
function BasicLayout(): ReactElement {
const headerLinks = [
<NavItem key='home' href='/' label='HOME' />,
<NavItem key='filing' href='/filing' label='FILING' />
<NavItem key='filing' href='/filing' label='FILING' />,
<NavItem key='profile-form' href='/profile-form' label='PROFILE FORM' />
];

const auth = useSblAuth();
Expand Down Expand Up @@ -88,16 +90,22 @@ function BasicLayout(): ReactElement {
);
}

function ProtectedRoute({ isAuthenticated, children }) {
interface ProtectedRouteProperties {
isAuthenticated: boolean;
children: ReactNode;
}

function ProtectedRoute({ isAuthenticated, children }: ProtectedRouteProperties): ReactNode {
if (!isAuthenticated) {
return <Navigate to="/home" replace />;
}
return children;
return children ;
}

export default function App(): ReactElement {
const auth = useSblAuth();


if (auth.isLoading) {
return (<>
Loading Auth...
Expand All @@ -117,6 +125,7 @@ export default function App(): ReactElement {
</ProtectedRoute>
}
/>
<Route path='/profile-form' element={<ProfileForm />} />
<Route path='/' element={<Navigate to='/filing' />} />
</Route>
<Route path='/*' element={<Navigate to='/home' />} />
Expand Down
1 change: 1 addition & 0 deletions src/assets/error.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/assets/warning-error.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions src/components/ErrorIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ReactComponent as ErrorSVG } from "assets/error.svg";


function ErrorIcon(): JSX.Element {
return (
<div className='text-errorColor'>
<ErrorSVG />
</div>
)
}

export default ErrorIcon
12 changes: 12 additions & 0 deletions src/components/WarningErrorIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ReactComponent as WarningErrorSVG } from "assets/warning-error.svg";


function WarningErrorIcon(): JSX.Element {
return (
<div className='text-errorColor'>
<WarningErrorSVG />
</div>
)
}

export default WarningErrorIcon
4 changes: 4 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@
&:hover {
color: #2284d5;
}
}

[type='text']:focus {
box-shadow: none;
}
36 changes: 36 additions & 0 deletions src/pages/ProfileForm/InputEntry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { ReactNode } from 'react';

interface InputEntryProperties {
id: string;
label: string;
errors: object;
isDisabled: boolean;
register: () => void;
children: ReactNode
}

function InputEntry({id, errors, label, register, isDisabled = false, children}: InputEntryProperties) {
return (
<div className="mb-6">
<label
htmlFor={id}
className="text-[1.125em] font-medium tracking-[inherit] leading-tight mb-2 inline-block w-full"
>
{label}
</label>
{children}
<input
type="text"
id={id}
className={`border w-full ${errors[id] ? 'border-errorColor border-2': ""} disabled:bg-disabledColor`}
{...register(id)}
disabled={isDisabled}
/>
{errors[id] ? <p className="text-base text-errorColor mt-2">
{errors[id].message}
</p> : null}
</div>
)
}

export default InputEntry;
27 changes: 27 additions & 0 deletions src/pages/ProfileForm/ProfileForm.data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
interface FiDataType {
bankName: string;
leiID: string;
agencyCode: number;
}

const fiData: FiDataType[] = [
{
bankName: "Suntrust Banks, Inc",
leiID: "7E1PDLW1JLaTSoBS1Go3",
agencyCode: 3
},
{
bankName: "JP Morgan, Inc",
leiID: "8E1ODLE1JLaSVoBS1Bo2",
agencyCode: 4
},
{
bankName: "Bank of America, Inc",
leiID: "3E89DLE1JBaLEoBS1Co1",
agencyCode: 4
},
];

export type { FiDataType };
export { fiData };

166 changes: 166 additions & 0 deletions src/pages/ProfileForm/Step1Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
/* eslint-disable react/jsx-props-no-spreading */
import { zodResolver } from "@hookform/resolvers/zod";
import useSblAuth from 'api/useSblAuth';
import type { SubmitHandler } from "react-hook-form";
import { useForm } from "react-hook-form";
import { z } from "zod";

import ErrorIcon from 'components/ErrorIcon';

import Select from "react-select";
import Step1FormHeader from "./Step1FormHeader";

import { Button, Link } from 'design-system-react';
import InputEntry from "./InputEntry";
import { fiData } from './ProfileForm.data';

const financialInstitutionsSchema = z.object({
label: z.string(),
value: z.string(),
});

type FinancialInstitution = z.infer<typeof financialInstitutionsSchema>;

const fiDataTypeSchema = z.object({
bankName: z.string(),
leiID: z.string(),
agencyCode: z.number()
})

const fiOptions: FinancialInstitution[] = fiData.map(object => ({
label: object.bankName,
value: object.leiID,
}));

const validationSchema = z
.object({
firstName: z
.string().min(1, { message: "First name is required" }),
lastName: z
.string().min(1, { message: "Last name is required" }),
email: z.string().min(2, { message: "Email is required" }).email({
message: "Must be a valid email",
}),
financialInstitutions: financialInstitutionsSchema
.array()
.min(1, { message: "Please pick at least one associated financial institution." }),
fiData: fiDataTypeSchema
.array()
.min(1, { message: "You should have associated financial institution information."})
});

type ValidationSchema = z.infer<typeof validationSchema>;

function Step1Form(): JSX.Element {
const auth = useSblAuth();
const email = auth.user?.profile.email;

const defaultValues: ValidationSchema = {
firstName: "",
lastName: "",
email: email ?? "",
financialInstitutions: [],
// fiData: fiData ?? []
fiData: []
};

const {
register,
handleSubmit,
setValue,
trigger,
getValues,
formState: { errors },
} = useForm<ValidationSchema>({
resolver: zodResolver(validationSchema),
defaultValues
});

const onSubmit: SubmitHandler<ValidationSchema> = (data) => {
console.log('data:', data);
}

console.log('errors:', errors)


const customStyles = {
control: (provided, state) => ({
...provided,
outline: state.isFocused ? '0.25rem solid #2491ff !important' : '',
outlineOffset: state.isFocused ? '0 !important' : ''
}),
};

return (
<div className="ml-5 mr-5">
<div className="max-w-[1200px] mx-auto mb-12">
<div className="max-w-[770px] mx-auto">
<Step1FormHeader />
<form
className="bg-[#F7F8F9] p-[30px] border"
onSubmit={handleSubmit(onSubmit)}
>
<InputEntry label="First name" id="firstName" register={register} errors={errors} isDisabled={false} />
<InputEntry label="Last name" id="lastName" register={register} errors={errors} isDisabled={false} />
<InputEntry label="Email address" id="email" register={register} errors={errors} isDisabled>
<p className="">Your email address is automatically pulled in from Login.gov.</p>
</InputEntry>

<div className="mt-8 mb-9">
<h4 className="text-[1.125em] font-medium tracking-[inherit] leading-tight mb-2 inline-block w-full">Associated financial institution(s)</h4>
<p className="">Select the financial institution(s) that you are associated with.</p>
<div className="mb-4">
<Select
classNames={{
control: (state) => `!rounded-none !border !w-full " : '!border-inherit' }`,
indicatorSeparator: (state) => '!mb-0 !mt-0 !border-inherit',
indicatorsContainer: (state) => '!bg-disabledColor',
dropdownIndicator: (state) => '!text-inherit',
valueContainer: ()=> `${ (errors.financialInstitutions ?? errors.fiData) ? "!border-errorColor !border-2 !border-solid" : ""}`,
}}
options={fiOptions}
isSearchable
placeholder=''
styles={customStyles}
/>
</div>
{errors.fiData ?
<div className="flex flex-row gap-3">
<ErrorIcon />
<div className='max-w-[587px]'>
<h4 className='text text-[14px] font-medium mb-[0.35rem] leading-[19px]'>No results found in our database.
</h4>
<p className='text text-[14px] leading-[0.95rem]'>The financial institution/LEI you search for war not found in our database. If you recently registered for an LEI with GLEIF, your registration may still be in process. if you need further assistance please <Link href="#">submit a technical question</Link> to our help desk.
</p>
</div>
</div>

: null}

</div>
<Button
appearance="primary"
onClick={async ()=>{
const passesValidation = await trigger();
if (passesValidation) {
// TODO: Post the submission
}
console.log("validationResult:", passesValidation)
// console.log("getValues:", getValues())
// console.log('onclick errors', errors);
}}
label="Submit"
size="default">
Submit
</Button>


</form>
</div>
</div>
</div>
);
}

export default Step1Form;
20 changes: 20 additions & 0 deletions src/pages/ProfileForm/Step1FormHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Link } from 'design-system-react';

/**
*
* @returns Header for Step1Form
*/
function Step1FormHeader(): JSX.Element {
return (
<div className="max-w-[670px]">
<h1 className="text-[26px] md:text-[30px] font-semibold leading-tight mb-[0.44117647em]">Complete your user profile</h1>
<p className="text-[18px] md:text-[20px] font-normal leading-tight mb-[30px]">In order to complete your user profile and access the filing platform your financial institution must have a Legal Entity Identifier (LEI). Visit the Global LEI Foundation (GLEIF) website for information on how to obtain an LEI for your institution.</p>
<p className="leading-paragraph mb-[30px]">
In order to begin using the filing platform you must have a Legal Entity identifier (LEI) for your financial institution. Visit the <Link href="#">Global LEI Foundation (GLEIF)</Link> website for more information on how to obtain an LEI.
</p>
<p />
</div>
)
}

export default Step1FormHeader;
Loading

0 comments on commit 9e33de0

Please sign in to comment.