-
Notifications
You must be signed in to change notification settings - Fork 1
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
Changes from all commits
7f68a3f
b5a1a81
d05614a
20d8fb5
ebb4e43
5967302
f1ad515
8d91e56
40a64b0
8b6553b
6389396
113a26f
911a0ec
e8e7f63
c9f9246
995c1c4
d0cf487
715eedd
ae05160
3384106
ff0e8f0
60b1b4a
d7411a7
ccaf2e4
c5e0f5d
6c94b45
a4f0cd1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
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 |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,4 +13,8 @@ | |
&:hover { | ||
color: #2284d5; | ||
} | ||
} | ||
|
||
[type='text']:focus { | ||
box-shadow: none; | ||
} |
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; |
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 }; | ||
|
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) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
Comment on lines
+80
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Fine if not...just contemplating how to make things more readable. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
}} | ||||||||||||||||||||||||||
label="Submit" | ||||||||||||||||||||||||||
size="default"> | ||||||||||||||||||||||||||
Submit | ||||||||||||||||||||||||||
</Button> | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
</form> | ||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
export default Step1Form; |
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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unintended duplicate paragraphs indicating user must have an LEI? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.