From 7daef8c621915263a74a221cadfd1c7944c1cfc1 Mon Sep 17 00:00:00 2001 From: Jared White Date: Fri, 17 Oct 2025 03:14:32 +0000 Subject: [PATCH] feat: migrate some forms away from many UIC deps, and support Enter submitting --- shared-helpers/index.ts | 1 + shared-helpers/src/views/CustomIconMap.tsx | 2 + .../views/components/BloomCard.module.scss | 1 + shared-helpers/src/views/components/Form.tsx | 21 +++++ .../forgot-password/FormForgotPassword.tsx | 3 +- .../src/views/sign-in/FormSignIn.module.scss | 16 +++- .../src/views/sign-in/FormSignIn.tsx | 3 + .../src/views/sign-in/FormSignInDefault.tsx | 42 +++++---- .../src/views/sign-in/FormSignInPwdless.tsx | 20 ++-- .../views/sign-in/ResendConfirmationModal.tsx | 3 +- .../components/users/FormSignIn.module.scss | 7 ++ .../components/users/FormSignInAddPhone.tsx | 54 +++++------ .../components/users/FormSignInMFACode.tsx | 60 ++++++------ .../components/users/FormSignInMFAType.tsx | 91 ++++++++----------- .../src/components/users/FormTerms.tsx | 55 +++++------ .../src/components/users/FormUserConfirm.tsx | 78 ++++++++-------- sites/partners/src/lib/users/signInHelpers.ts | 16 +++- sites/partners/src/pages/reset-password.tsx | 38 ++++---- sites/partners/src/pages/sign-in.tsx | 9 +- sites/partners/src/pages/users/confirm.tsx | 17 +--- sites/partners/styles/overrides.scss | 34 +++++++ 21 files changed, 310 insertions(+), 261 deletions(-) create mode 100644 shared-helpers/src/views/components/Form.tsx create mode 100644 sites/partners/src/components/users/FormSignIn.module.scss diff --git a/shared-helpers/index.ts b/shared-helpers/index.ts index 4680ea1ce7..c5896893da 100644 --- a/shared-helpers/index.ts +++ b/shared-helpers/index.ts @@ -29,6 +29,7 @@ export * from "./src/utilities/token" export * from "./src/utilities/unitTypes" export * from "./src/views/components/BloomCard" export * from "./src/views/components/ClickableCard" +export * from "./src/views/components/Form" export * from "./src/views/CustomIconMap" export * from "./src/views/forgot-password/FormForgotPassword" export * from "./src/views/layout/ExygyFooter" diff --git a/shared-helpers/src/views/CustomIconMap.tsx b/shared-helpers/src/views/CustomIconMap.tsx index 838f326614..163bfcc2d3 100644 --- a/shared-helpers/src/views/CustomIconMap.tsx +++ b/shared-helpers/src/views/CustomIconMap.tsx @@ -3,6 +3,7 @@ import { Application, Door, Profile } from "./CustomIcons" import LockClosedIcon from "@heroicons/react/24/solid/LockClosedIcon" import ChevronLeftIcon from "@heroicons/react/20/solid/ChevronLeftIcon" import Clock from "@heroicons/react/24/outline/ClockIcon" +import Cog8ToothIcon from "@heroicons/react/24/solid/Cog8ToothIcon" import HeartIcon from "@heroicons/react/24/outline/HeartIcon" import HeartIconSolid from "@heroicons/react/24/solid/HeartIcon" import HomeModernIcon from "@heroicons/react/24/outline/HomeModernIcon" @@ -19,6 +20,7 @@ export const CustomIconMap = { lockClosed: , chevronLeft: , clock: , + cog: , heartIcon: , heartIconSolid: , home: , diff --git a/shared-helpers/src/views/components/BloomCard.module.scss b/shared-helpers/src/views/components/BloomCard.module.scss index a6d421cf0c..8565c7b900 100644 --- a/shared-helpers/src/views/components/BloomCard.module.scss +++ b/shared-helpers/src/views/components/BloomCard.module.scss @@ -1,4 +1,5 @@ .form-card { + --card-footer-background-color: var(--seeds-bg-color-surface-primary); --card-content-padding-inline: var(--seeds-s12); --card-header-padding-inline: var(--seeds-s12); @media (max-width: theme("screens.sm")) { diff --git a/shared-helpers/src/views/components/Form.tsx b/shared-helpers/src/views/components/Form.tsx new file mode 100644 index 0000000000..a2f1885f3e --- /dev/null +++ b/shared-helpers/src/views/components/Form.tsx @@ -0,0 +1,21 @@ +import React from "react" + +interface FormProps { + children: React.ReactNode + id?: string + className?: string + disableSubmitOnEnter?: boolean + onSubmit?: () => unknown +} + +export const Form = ({ id, children, className, disableSubmitOnEnter, onSubmit }: FormProps) => { + function onKeyDown(e: React.KeyboardEvent) { + if (disableSubmitOnEnter && e.key === "Enter" && !(e.target instanceof HTMLButtonElement)) e.preventDefault() + } + + return ( +
+ {children} +
+ ) +} diff --git a/shared-helpers/src/views/forgot-password/FormForgotPassword.tsx b/shared-helpers/src/views/forgot-password/FormForgotPassword.tsx index ca7bbc41ac..c8bd2e6a3d 100644 --- a/shared-helpers/src/views/forgot-password/FormForgotPassword.tsx +++ b/shared-helpers/src/views/forgot-password/FormForgotPassword.tsx @@ -1,6 +1,6 @@ import React from "react" import { Button } from "@bloom-housing/ui-seeds" -import { Field, Form, t, AlertBox, AlertNotice, ErrorMessage } from "@bloom-housing/ui-components" +import { Field, t, AlertBox, AlertNotice, ErrorMessage } from "@bloom-housing/ui-components" import { CardSection } from "@bloom-housing/ui-seeds/src/blocks/Card" import { NetworkErrorReset, NetworkStatusContent } from "../../auth/catchNetworkError" import type { UseFormMethods } from "react-hook-form" @@ -8,6 +8,7 @@ import { BloomCard } from "../components/BloomCard" import { emailRegex } from "../../utilities/regex" import styles from "./FormForgotPassword.module.scss" import { useRouter } from "next/router" +import { Form } from "../components/Form" export type FormForgotPasswordProps = { control: FormForgotPasswordControl diff --git a/shared-helpers/src/views/sign-in/FormSignIn.module.scss b/shared-helpers/src/views/sign-in/FormSignIn.module.scss index 76bab83361..6b23d9175f 100644 --- a/shared-helpers/src/views/sign-in/FormSignIn.module.scss +++ b/shared-helpers/src/views/sign-in/FormSignIn.module.scss @@ -1,8 +1,17 @@ .forgot-password { - float: inline-end; font-size: var(--seeds-font-size-sm); - text-decoration-line: underline; - color: var(--seeds-color-blue-900); +} + +.forgot-password-wrapper { + grid-area: field; + width: fit-content; + justify-self: end; + text-align: right; +} + +.password-grid { + display: grid; + grid-template-areas: "field"; } .sign-in-error-container { @@ -24,6 +33,7 @@ .sign-in-password-input { margin-bottom: var(--seeds-s3); + grid-area: field; } .sign-in-action { diff --git a/shared-helpers/src/views/sign-in/FormSignIn.tsx b/shared-helpers/src/views/sign-in/FormSignIn.tsx index 6204feb755..b0a94e5f0b 100644 --- a/shared-helpers/src/views/sign-in/FormSignIn.tsx +++ b/shared-helpers/src/views/sign-in/FormSignIn.tsx @@ -19,6 +19,9 @@ export type FormSignInProps = { export type FormSignInControl = { errors: UseFormMethods["errors"] + handleSubmit?: UseFormMethods["handleSubmit"] + register?: UseFormMethods["register"] + watch?: UseFormMethods["watch"] } export type FormSignInValues = { diff --git a/shared-helpers/src/views/sign-in/FormSignInDefault.tsx b/shared-helpers/src/views/sign-in/FormSignInDefault.tsx index ffd92bda72..08744cfbdb 100644 --- a/shared-helpers/src/views/sign-in/FormSignInDefault.tsx +++ b/shared-helpers/src/views/sign-in/FormSignInDefault.tsx @@ -1,9 +1,10 @@ import React, { useContext } from "react" import { useRouter } from "next/router" import type { UseFormMethods } from "react-hook-form" -import { Field, Form, NavigationContext, t } from "@bloom-housing/ui-components" -import { Button } from "@bloom-housing/ui-seeds" +import { Field, t } from "@bloom-housing/ui-components" +import { Button, Link } from "@bloom-housing/ui-seeds" import { getListingRedirectUrl } from "../../utilities/getListingRedirectUrl" +import { Form } from "../components/Form" import styles from "./FormSignIn.module.scss" export type FormSignInDefaultProps = { @@ -31,7 +32,6 @@ const FormSignInDefault = ({ const onError = () => { window.scrollTo(0, 0) } - const { LinkComponent } = useContext(NavigationContext) const router = useRouter() const listingIdRedirect = router.query?.listingId as string const forgetPasswordURL = getListingRedirectUrl(listingIdRedirect, "/forgot-password") @@ -49,23 +49,25 @@ const FormSignInDefault = ({ register={register} dataTestId="sign-in-email-field" /> - - +
+ + +
-
- - - + + + + ) } diff --git a/sites/partners/src/components/users/FormSignInMFACode.tsx b/sites/partners/src/components/users/FormSignInMFACode.tsx index dcd5da8764..35b181308c 100644 --- a/sites/partners/src/components/users/FormSignInMFACode.tsx +++ b/sites/partners/src/components/users/FormSignInMFACode.tsx @@ -1,15 +1,14 @@ import React from "react" +import { Field, t } from "@bloom-housing/ui-components" +import { Button, Card } from "@bloom-housing/ui-seeds" import { - Field, + BloomCard, Form, - FormCard, - t, + FormSignInControl, FormSignInErrorBox, NetworkStatus, - FormSignInControl, -} from "@bloom-housing/ui-components" -import { Button, Icon } from "@bloom-housing/ui-seeds" -import { CustomIconMap } from "@bloom-housing/shared-helpers" +} from "@bloom-housing/shared-helpers" +import styles from "./FormSignIn.module.scss" export enum RequestType { email = "email", @@ -57,24 +56,23 @@ const FormSignInMFACode = ({ } return ( - -
- {CustomIconMap.profile} -

{t("nav.signInMFA.verifyTitle")}

-

- {mfaType === RequestType.sms - ? t("nav.signInMFA.haveSentCodeToPhone") - : t("nav.signInMFA.haveSentCodeToEmail")} -

-
- - -
-
+ + + + -
+ + + -
- -
-
+ + + + ) } diff --git a/sites/partners/src/components/users/FormSignInMFAType.tsx b/sites/partners/src/components/users/FormSignInMFAType.tsx index 6cf8a30b21..b666e4a725 100644 --- a/sites/partners/src/components/users/FormSignInMFAType.tsx +++ b/sites/partners/src/components/users/FormSignInMFAType.tsx @@ -1,31 +1,23 @@ import React from "react" +import { Field, t } from "@bloom-housing/ui-components" +import { Button, Card } from "@bloom-housing/ui-seeds" import { - Field, + BloomCard, Form, - FormCard, - t, + FormSignInControl, FormSignInErrorBox, NetworkStatus, -} from "@bloom-housing/ui-components" -import { Button, Icon } from "@bloom-housing/ui-seeds" -import type { UseFormMethods } from "react-hook-form" -import { CustomIconMap } from "@bloom-housing/shared-helpers" +} from "@bloom-housing/shared-helpers" +import styles from "./FormSignIn.module.scss" export type FormSignInMFAProps = { - control: FormSignInMFAControl + control: FormSignInControl onSubmit: (data: unknown) => void networkError: NetworkStatus emailOnClick: () => void smsOnClick: () => void } -export type FormSignInMFAControl = { - errors: UseFormMethods["errors"] - handleSubmit: UseFormMethods["handleSubmit"] - register: UseFormMethods["register"] - setValue: UseFormMethods["setValue"] -} - const FormSignInMFAType = ({ onSubmit, networkError, @@ -37,40 +29,37 @@ const FormSignInMFAType = ({ window.scrollTo(0, 0) } + const subTitle = process.env.showSmsMfa ? t("nav.signInMFA.verificationChoiceSecondaryTitle") : undefined + return ( - -
- {CustomIconMap.profile} -

- {t("nav.signInMFA.verificationChoiceMainTitle")} -

- {process.env.showSmsMfa && ( -

- {t("nav.signInMFA.verificationChoiceSecondaryTitle")} -

+ +
+ + + {subTitle && ( + +

{subTitle}

+
)} -
- -
- -
-
+ + + ) } diff --git a/sites/partners/src/components/users/FormTerms.tsx b/sites/partners/src/components/users/FormTerms.tsx index 53c9705fa3..36c403a2bc 100644 --- a/sites/partners/src/components/users/FormTerms.tsx +++ b/sites/partners/src/components/users/FormTerms.tsx @@ -1,9 +1,9 @@ import React from "react" -import { Field, Form, FormCard, MarkdownSection, t } from "@bloom-housing/ui-components" -import { Button, Icon } from "@bloom-housing/ui-seeds" -import Cog8ToothIcon from "@heroicons/react/24/solid/Cog8ToothIcon" +import { Field, t } from "@bloom-housing/ui-components" +import { Button, Card } from "@bloom-housing/ui-seeds" import Markdown from "markdown-to-jsx" import { useForm } from "react-hook-form" +import { BloomCard, Form } from "@bloom-housing/shared-helpers" type FormTermsInValues = { agree: boolean @@ -19,32 +19,26 @@ const FormTerms = (props: FormTermsProps) => { const { handleSubmit, register, errors } = useForm() return ( -
- -
- - - -

{t(`authentication.terms.reviewToc`)}

-

- {t(`authentication.terms.youMustAcceptToc`)} -

- -
+ + + +
{props.terms && ( - - {props.terms} - + + {props.terms} + )}
-
- -
+ + { errorMessage={t("errors.agreeError")} dataTestId="agree" /> -
- -
- -
-
+ + + -
-
- - + + + + ) } diff --git a/sites/partners/src/components/users/FormUserConfirm.tsx b/sites/partners/src/components/users/FormUserConfirm.tsx index 594eded05a..7be639e3ad 100644 --- a/sites/partners/src/components/users/FormUserConfirm.tsx +++ b/sites/partners/src/components/users/FormUserConfirm.tsx @@ -1,12 +1,17 @@ import React, { useRef, useContext, useEffect, useState } from "react" import { useRouter } from "next/router" -import { t, FormCard, Form, Field, useMutate, AlertBox } from "@bloom-housing/ui-components" -import { Button, Dialog, Icon } from "@bloom-housing/ui-seeds" -import { AuthContext, MessageContext, passwordRegex } from "@bloom-housing/shared-helpers" +import { t, Field, useMutate } from "@bloom-housing/ui-components" +import { Alert, Button, Card, Dialog } from "@bloom-housing/ui-seeds" +import { + AuthContext, + BloomCard, + Form, + MessageContext, + passwordRegex, +} from "@bloom-housing/shared-helpers" import { useForm } from "react-hook-form" import { ReRequestConfirmation } from "./ReRequestConfirmation" import { SuccessDTO } from "@bloom-housing/shared-helpers/src/types/backend-swagger" -import Cog8ToothIcon from "@heroicons/react/24/solid/Cog8ToothIcon" type FormUserConfirmFields = { password: string @@ -81,44 +86,38 @@ const FormUserConfirm = () => { if (!token) { return ( - -
- {t(`authentication.createAccount.errors.tokenMissing`)} -
-
+ + {t(`authentication.createAccount.errors.tokenMissing`)} + ) } return ( <> - -
- - - -

{t("users.addPassword")}

-

{t("users.needUniquePassword")}

- - {isError && ( - - {t(`errors.alert.badRequest`)} - - )} - - {newConfirmationRequested && ( - - {t(`users.confirmationSent`)} - - )} -
- +
-
+ + {isError && ( + + {t(`errors.alert.badRequest`)} + + )} + + {newConfirmationRequested && ( + + {t(`users.confirmationSent`)} + + )} +
- + {t("authentication.createAccount.password")} -

{t("users.makeNote")}

+

{t("users.makeNote")}

{ />
-
- -
-
+ + + -
-
+ +
-
+ async (data: { phoneNumber: string }) => { const { phoneNumber } = data - await requestMfaCode(email, password, mfaType, phoneNumber) - resetNetworkError() - setRenderStep(EnumRenderStep.enterCode) - setAllowPhoneNumberEdit(true) - setPhoneNumber(phoneNumber) + try { + await requestMfaCode(email, password, mfaType, phoneNumber) + resetNetworkError() + setRenderStep(EnumRenderStep.enterCode) + setAllowPhoneNumberEdit(true) + setPhoneNumber(phoneNumber) + } catch(error) { + const { status } = error.response || {} + determineNetworkError(status, error, true) + } } export const onSubmitMfaCode = diff --git a/sites/partners/src/pages/reset-password.tsx b/sites/partners/src/pages/reset-password.tsx index d71e5bb8e2..14d0b2b8b6 100644 --- a/sites/partners/src/pages/reset-password.tsx +++ b/sites/partners/src/pages/reset-password.tsx @@ -1,9 +1,9 @@ import React, { useState, useContext, useRef } from "react" import { useRouter } from "next/router" import { useForm } from "react-hook-form" -import { Button, Icon } from "@bloom-housing/ui-seeds" -import { Field, Form, FormCard, t, AlertBox } from "@bloom-housing/ui-components" -import { AuthContext, CustomIconMap, MessageContext } from "@bloom-housing/shared-helpers" +import { Alert, Button, Card } from "@bloom-housing/ui-seeds" +import { Field, t } from "@bloom-housing/ui-components" +import { AuthContext, BloomCard, Form, MessageContext } from "@bloom-housing/shared-helpers" import FormsLayout from "../layouts/forms" const ResetPassword = () => { @@ -42,18 +42,13 @@ const ResetPassword = () => { return ( - -
- {CustomIconMap.profile} -

{t("authentication.forgotPassword.changePassword")}

-
- {requestError && ( - setRequestError(undefined)} type="alert"> - {requestError} - - )} -
-
+ + + + {requestError && ( + setRequestError(undefined)}>{requestError} + )} + { type="password" labelClassName={"text__caps-spaced"} /> - -
+ + + -
- -
-
+ + + +
) } diff --git a/sites/partners/src/pages/sign-in.tsx b/sites/partners/src/pages/sign-in.tsx index f2fcebd03a..d4bed2ff9e 100644 --- a/sites/partners/src/pages/sign-in.tsx +++ b/sites/partners/src/pages/sign-in.tsx @@ -191,9 +191,9 @@ const SignIn = () => { )} emailOnClick={() => setValue("mfaType", MfaType.email)} smsOnClick={() => setValue("mfaType", MfaType.sms)} - control={{ register, errors, handleSubmit, setValue }} + control={{ register, errors, handleSubmit }} networkError={{ - content: { ...networkError, error: !!networkError?.error }, + content: { ...networkError }, reset: resetNetworkError, }} /> @@ -207,13 +207,14 @@ const SignIn = () => { mfaType, setRenderStep, requestMfaCode, + determineNetworkError, setAllowPhoneNumberEdit, setPhoneNumber, resetNetworkError )} control={{ errors, handleSubmit, control }} networkError={{ - content: { ...networkError, error: !!networkError?.error }, + content: { ...networkError }, reset: resetNetworkError, }} phoneNumber={phoneNumber} @@ -233,7 +234,7 @@ const SignIn = () => { )} control={{ register, errors, handleSubmit, watch }} networkError={{ - content: { ...networkError, error: !!networkError?.error }, + content: { ...networkError }, reset: resetNetworkError, }} mfaType={mfaType} diff --git a/sites/partners/src/pages/users/confirm.tsx b/sites/partners/src/pages/users/confirm.tsx index 86cad21284..cbe7e61b2b 100644 --- a/sites/partners/src/pages/users/confirm.tsx +++ b/sites/partners/src/pages/users/confirm.tsx @@ -1,22 +1,13 @@ import React from "react" -import Head from "next/head" -import Layout from "../../layouts" import { t } from "@bloom-housing/ui-components" +import FormsLayout from "../../layouts/forms" import { FormUserConfirm } from "../../components/users/FormUserConfirm" const ConfirmPage = () => { return ( - - - {`User Confirm - ${t("nav.siteTitlePartners")}`} - - -
-
- -
-
-
+ + + ) } diff --git a/sites/partners/styles/overrides.scss b/sites/partners/styles/overrides.scss index 7b2707cdd0..225e287e0d 100644 --- a/sites/partners/styles/overrides.scss +++ b/sites/partners/styles/overrides.scss @@ -25,6 +25,40 @@ .seeds-button[data-variant="primary"] { color: white; // AG grid styling from UIC was messing up our primary button } + + .bloom-markdown { // copied over from public + &, + > * { + overflow-wrap: anywhere; + } + + p:not(li > *) { + padding-block: 0.25em; + } + + ul { + list-style: disc; + } + + ol { + list-style: decimal; + } + ul, + ol { + margin-inline-start: var(--seeds-s5); + margin-block-end: 0.5em; + } + + hr { + margin-block: var(--seeds-spacer-label); + width: 100%; + } + + a { + color: var(--seeds-color-blue-500); + text-decoration: underline; + } + } } .section-with-grid hgroup p {