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

[User Profile] - Layout View - Initial User Profile Form, Frontend Validation Schema #27

Merged
merged 27 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7f68a3f
feature: Profile Form -- Start
shindigira Aug 14, 2023
b5a1a81
feat: Profile form
shindigira Aug 14, 2023
d05614a
merged from 'main'
shindigira Aug 15, 2023
20d8fb5
feat: zod validation update
shindigira Aug 16, 2023
ebb4e43
feat: User Profile - Step 1
shindigira Aug 17, 2023
5967302
chore: Auto-populate email in User Profile with logged-in user email …
shindigira Aug 17, 2023
f1ad515
feat: Integrated Zustand/Immer, Step Forms
shindigira Aug 17, 2023
8d91e56
chore: Added 'login.gov' notice to email field
shindigira Aug 18, 2023
40a64b0
chore: red border on missing first and last name fields
shindigira Aug 18, 2023
8b6553b
feat: added mock associated financial data for the validation schema
shindigira Aug 18, 2023
6389396
chore: InputEntry component
shindigira Aug 18, 2023
113a26f
chore: added and yarn cache items
shindigira Aug 18, 2023
911a0ec
fix: added error border styling for the associated financial institut…
shindigira Aug 18, 2023
e8e7f63
feat: Initial User Profile form complete
shindigira Aug 19, 2023
c9f9246
chore: set a link - submit a technical question
shindigira Aug 19, 2023
995c1c4
chore: added default margin for mobile responsiveness
shindigira Aug 19, 2023
d0cf487
fix: removed dead code
shindigira Aug 20, 2023
715eedd
chore: fixed form padding
shindigira Aug 21, 2023
ae05160
chore: Tailwind colors, Error SVG correction
shindigira Aug 22, 2023
3384106
chore: added additional notice regarding LEI to the User Profile form
shindigira Aug 22, 2023
ff0e8f0
rebased off of 'main'
shindigira Aug 24, 2023
60b1b4a
chore: re-added @cfpb/cfpb-design-system
shindigira Aug 24, 2023
d7411a7
fix: removed `console.log`
shindigira Aug 24, 2023
ccaf2e4
Update src/pages/ProfileForm/Step1Form.tsx
shindigira Aug 24, 2023
c5e0f5d
Update src/pages/ProfileForm/Step1Form.tsx
shindigira Aug 24, 2023
6c94b45
Update src/pages/ProfileForm/Step1FormHeader.tsx
shindigira Aug 24, 2023
a4f0cd1
fix: fixed missing char
shindigira Aug 24, 2023
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
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 ?? []
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// 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)

Comment on lines +80 to +85
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm guessing there is no API endpoint for this data yet? This PR is just the UI?

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@meissadia Not wired to a backend yet. Will mock some API calls soon.


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">
Comment on lines +96 to +98
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it too early to turn these Tailwinded wrappers into reusable Form components? i.e. Do you think these styles would be used in other Steps and we could abstract this into a <FormWrapper> or something like that?

Fine if not...just contemplating how to make things more readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved in 843c4bf

<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);
Comment on lines +150 to +151
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// 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>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Unintended duplicate paragraphs indicating user must have an LEI?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved

<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
Loading