@bloom-housing/ui-components 4.0.3 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.jest/setup-tests.js +8 -0
  2. package/CHANGELOG.md +534 -37
  3. package/README.md +1 -1
  4. package/index.ts +8 -4
  5. package/package.json +4 -4
  6. package/src/authentication/AuthContext.ts +32 -3
  7. package/src/blocks/FormCard.scss +12 -0
  8. package/src/blocks/ImageCard.scss +10 -8
  9. package/src/blocks/ViewItem.tsx +5 -1
  10. package/src/config/NavigationContext.tsx +4 -0
  11. package/src/forms/DOBField.tsx +1 -1
  12. package/src/forms/Field.tsx +4 -2
  13. package/src/forms/FieldGroup.tsx +27 -14
  14. package/src/global/headers.scss +7 -3
  15. package/src/global/lists.scss +4 -5
  16. package/src/global/tables.scss +3 -1
  17. package/src/headers/PageHeader.tsx +5 -1
  18. package/src/helpers/tableSummaries.tsx +1 -1
  19. package/src/helpers/useIntersect.ts +48 -0
  20. package/src/icons/Icon.tsx +6 -1
  21. package/src/icons/Icons.tsx +1 -1
  22. package/src/locales/es.json +1 -1
  23. package/src/locales/general.json +37 -5
  24. package/src/locales/vi.json +1 -1
  25. package/src/locales/zh.json +1 -1
  26. package/src/notifications/AlertBox.scss +3 -3
  27. package/src/notifications/AlertBox.tsx +3 -1
  28. package/src/notifications/AlertNotice.tsx +6 -1
  29. package/src/notifications/ApplicationStatus.scss +2 -7
  30. package/src/notifications/ApplicationStatus.tsx +10 -13
  31. package/src/overlays/Modal.tsx +2 -0
  32. package/src/overlays/Overlay.scss +8 -0
  33. package/src/overlays/Overlay.tsx +2 -1
  34. package/src/page_components/forgot-password/FormForgotPassword.tsx +114 -0
  35. package/src/page_components/listing/ContentAccordion.scss +34 -0
  36. package/src/page_components/listing/ContentAccordion.tsx +77 -0
  37. package/src/page_components/listing/ListingMap.scss +4 -0
  38. package/src/page_components/listing/ListingMap.tsx +13 -3
  39. package/src/page_components/listing/UnitTables.tsx +37 -27
  40. package/src/page_components/listing/listing_sidebar/events/DownloadLotteryResults.tsx +21 -22
  41. package/src/page_components/listing/listing_sidebar/events/EventSection.tsx +54 -0
  42. package/src/page_components/sign-in/FormSignIn.tsx +9 -33
  43. package/src/page_components/sign-in/FormSignInAddPhone.tsx +87 -0
  44. package/src/page_components/sign-in/FormSignInErrorBox.tsx +43 -0
  45. package/src/page_components/sign-in/FormSignInMFACode.tsx +98 -0
  46. package/src/page_components/sign-in/FormSignInMFAType.tsx +95 -0
  47. package/src/tables/StackedTable.tsx +1 -1
  48. package/src/page_components/listing/listing_sidebar/events/EventDateSection.tsx +0 -25
  49. package/src/page_components/listing/listing_sidebar/events/LotteryResultsEvent.tsx +0 -26
  50. package/src/page_components/listing/listing_sidebar/events/OpenHouseEvent.tsx +0 -27
  51. package/src/page_components/listing/listing_sidebar/events/PublicLotteryEvent.tsx +0 -22
@@ -8,23 +8,14 @@ import {
8
8
  Icon,
9
9
  LinkButton,
10
10
  t,
11
- AlertBox,
12
- SiteAlert,
13
- AlertNotice,
14
- ErrorMessage,
11
+ FormSignInErrorBox,
15
12
  } from "@bloom-housing/ui-components"
16
13
  import type { UseFormMethods } from "react-hook-form"
17
14
  import { NavigationContext } from "../../config/NavigationContext"
18
-
19
- export type NetworkErrorValue = {
20
- title: string
21
- content: string
22
- } | null
15
+ import type { NetworkErrorReset, NetworkErrorValue } from "../forgot-password/FormForgotPassword"
23
16
 
24
17
  export type NetworkErrorDetermineError = (status: number, error: Error) => void
25
18
 
26
- export type NetworkErrorReset = () => void
27
-
28
19
  export type FormSignInProps = {
29
20
  control: FormSignInControl
30
21
  onSubmit: (data: FormSignInValues) => void
@@ -61,31 +52,16 @@ const FormSignIn = ({
61
52
 
62
53
  return (
63
54
  <FormCard>
64
- <div className="form-card__lead text-center border-b mx-0">
55
+ <div className="form-card__lead text-center">
65
56
  <Icon size="2xl" symbol="profile" />
66
57
  <h2 className="form-card__title">{t(`nav.signIn`)}</h2>
67
58
  </div>
68
-
69
- {Object.entries(errors).length > 0 && !networkError.error && (
70
- <AlertBox type="alert" inverted closeable>
71
- {errors.authentication ? errors.authentication.message : t("errors.errorsToResolve")}
72
- </AlertBox>
73
- )}
74
-
75
- {!!networkError.error && Object.entries(errors).length === 0 && (
76
- <ErrorMessage id={"householdsize-error"} error={!!networkError.error}>
77
- <AlertBox type="alert" inverted onClose={() => networkError.reset()}>
78
- {networkError.error.title}
79
- </AlertBox>
80
-
81
- <AlertNotice title="" type="alert" inverted>
82
- {networkError.error.content}
83
- </AlertNotice>
84
- </ErrorMessage>
85
- )}
86
-
87
- <SiteAlert type="notice" dismissable />
88
- <div className="form-card__group pt-0 border-b">
59
+ <FormSignInErrorBox
60
+ errors={errors}
61
+ networkError={networkError}
62
+ errorMessageId={"main-sign-in"}
63
+ />
64
+ <div className="form-card__group pt-0">
89
65
  <Form id="sign-in" className="mt-10" onSubmit={handleSubmit(onSubmit, onError)}>
90
66
  <Field
91
67
  caps={true}
@@ -0,0 +1,87 @@
1
+ import React from "react"
2
+ import {
3
+ AppearanceStyleType,
4
+ Button,
5
+ Form,
6
+ FormCard,
7
+ Icon,
8
+ t,
9
+ SiteAlert,
10
+ PhoneField,
11
+ FormSignInErrorBox,
12
+ } from "@bloom-housing/ui-components"
13
+ import type { UseFormMethods } from "react-hook-form"
14
+ import { FormSignInNetworkError } from "./FormSignIn"
15
+
16
+ export type FormSignInAddPhoneProps = {
17
+ control: FormSignInAddPhoneControl
18
+ onSubmit: (data: FormSignInAddPhoneValues) => void
19
+ networkError: FormSignInNetworkError
20
+ phoneNumber: string
21
+ }
22
+
23
+ export type FormSignInAddPhoneControl = {
24
+ errors: UseFormMethods["errors"]
25
+ handleSubmit: UseFormMethods["handleSubmit"]
26
+ control: UseFormMethods["control"]
27
+ }
28
+
29
+ export type FormSignInAddPhoneValues = {
30
+ phoneNumber: string
31
+ }
32
+
33
+ const FormSignInAddPhone = ({
34
+ onSubmit,
35
+ networkError,
36
+ control,
37
+ phoneNumber,
38
+ }: FormSignInAddPhoneProps) => {
39
+ const onError = () => {
40
+ window.scrollTo(0, 0)
41
+ }
42
+ const { errors, handleSubmit } = control
43
+ return (
44
+ <FormCard>
45
+ <div className="form-card__lead text-center">
46
+ <Icon size="2xl" symbol="profile" className="form-card__header-icon" />
47
+ <h2 className="form-card__title is-borderless">{t("nav.signInMFA.addNumber")}</h2>
48
+ <p className="form-card__sub-title">{t("nav.signInMFA.addNumberSecondaryTitle")}</p>
49
+ </div>
50
+ <FormSignInErrorBox
51
+ errors={errors}
52
+ networkError={networkError}
53
+ errorMessageId={"add-phone"}
54
+ />
55
+
56
+ <SiteAlert type="notice" dismissable />
57
+ <div className="form-card__group pt-0">
58
+ <Form id="sign-in-mfa" className="mt-10" onSubmit={handleSubmit(onSubmit, onError)}>
59
+ <PhoneField
60
+ label={t("nav.signInMFA.phoneNumber")}
61
+ caps={true}
62
+ required={true}
63
+ id="phoneNumber"
64
+ name="phoneNumber"
65
+ error={errors.phoneNumber}
66
+ errorMessage={t("errors.phoneNumberError")}
67
+ controlClassName="control"
68
+ control={control.control}
69
+ dataTestId={"sign-in-phone-number-field"}
70
+ defaultValue={phoneNumber}
71
+ />
72
+
73
+ <div className="text-center mt-10">
74
+ <Button
75
+ styleType={AppearanceStyleType.primary}
76
+ data-test-id="request-mfa-code-and-add-phone"
77
+ >
78
+ {t("nav.signInMFA.addPhoneNumber")}
79
+ </Button>
80
+ </div>
81
+ </Form>
82
+ </div>
83
+ </FormCard>
84
+ )
85
+ }
86
+
87
+ export { FormSignInAddPhone as default, FormSignInAddPhone }
@@ -0,0 +1,43 @@
1
+ import React from "react"
2
+ import { t, AlertBox, SiteAlert, AlertNotice, ErrorMessage } from "@bloom-housing/ui-components"
3
+ import type { UseFormMethods } from "react-hook-form"
4
+ import { FormSignInNetworkError } from "./FormSignIn"
5
+
6
+ export type FormSignInErrorBoxProps = {
7
+ errors: FormSignInErrorBoxControl["errors"]
8
+ networkError: FormSignInNetworkError
9
+ errorMessageId: string
10
+ }
11
+
12
+ export type FormSignInErrorBoxControl = {
13
+ errors: UseFormMethods["errors"]
14
+ control: UseFormMethods["control"]
15
+ }
16
+
17
+ const FormSignInErrorBox = ({ networkError, errors, errorMessageId }: FormSignInErrorBoxProps) => {
18
+ return (
19
+ <div className="border-b">
20
+ {Object.entries(errors).length > 0 && !networkError.error && (
21
+ <AlertBox type="alert" inverted closeable>
22
+ {errors.authentication ? errors.authentication.message : t("errors.errorsToResolve")}
23
+ </AlertBox>
24
+ )}
25
+
26
+ {!!networkError.error && Object.entries(errors).length === 0 && (
27
+ <ErrorMessage id={`form-sign-in-${errorMessageId}-error`} error={!!networkError.error}>
28
+ <AlertBox type="alert" inverted onClose={() => networkError.reset()}>
29
+ {networkError.error.title}
30
+ </AlertBox>
31
+
32
+ <AlertNotice title="" type="alert" inverted>
33
+ {networkError.error.content}
34
+ </AlertNotice>
35
+ </ErrorMessage>
36
+ )}
37
+
38
+ <SiteAlert type="notice" dismissable />
39
+ </div>
40
+ )
41
+ }
42
+
43
+ export { FormSignInErrorBox as default, FormSignInErrorBox }
@@ -0,0 +1,98 @@
1
+ import React from "react"
2
+ import {
3
+ AppearanceStyleType,
4
+ Button,
5
+ Field,
6
+ Form,
7
+ FormCard,
8
+ Icon,
9
+ t,
10
+ SiteAlert,
11
+ FormSignInErrorBox,
12
+ } from "@bloom-housing/ui-components"
13
+ import { FormSignInNetworkError, FormSignInControl } from "./FormSignIn"
14
+ import { EnumRequestMfaCodeMfaType } from "@bloom-housing/backend-core/types"
15
+
16
+ export type FormSignInMFACodeProps = {
17
+ control: FormSignInControl
18
+ onSubmit: (data: FormSignInMFACodeValues) => void
19
+ networkError: FormSignInNetworkError
20
+ mfaType: EnumRequestMfaCodeMfaType
21
+ allowPhoneNumberEdit: boolean
22
+ phoneNumber: string
23
+ goBackToPhone: () => void
24
+ }
25
+
26
+ export type FormSignInMFACodeValues = {
27
+ mfaCode: string
28
+ }
29
+
30
+ const FormSignInMFACode = ({
31
+ onSubmit,
32
+ networkError,
33
+ control: { errors, register, handleSubmit },
34
+ mfaType,
35
+ allowPhoneNumberEdit,
36
+ phoneNumber,
37
+ goBackToPhone,
38
+ }: FormSignInMFACodeProps) => {
39
+ const onError = () => {
40
+ window.scrollTo(0, 0)
41
+ }
42
+
43
+ let note
44
+ if (allowPhoneNumberEdit) {
45
+ note = (
46
+ <>
47
+ {t("nav.signInMFA.sentTo", { phoneNumber })}{" "}
48
+ <Button
49
+ unstyled={true}
50
+ className=".field-note underline cursor-pointer font-semibold m-0"
51
+ onClick={() => goBackToPhone()}
52
+ >
53
+ {" "}
54
+ {t("nav.signInMFA.editPhoneNumber")}{" "}
55
+ </Button>
56
+ </>
57
+ )
58
+ }
59
+
60
+ return (
61
+ <FormCard>
62
+ <div className="form-card__lead text-center">
63
+ <Icon size="2xl" symbol="profile" className="form-card__header-icon" />
64
+ <h2 className="form-card__title is-borderless">{t("nav.signInMFA.verifyTitle")}</h2>
65
+ <p className="form-card__sub-title">
66
+ {mfaType === EnumRequestMfaCodeMfaType.sms
67
+ ? t("nav.signInMFA.haveSentCodeToPhone")
68
+ : t("nav.signInMFA.haveSentCodeToEmail")}
69
+ </p>
70
+ </div>
71
+ <FormSignInErrorBox errors={errors} networkError={networkError} errorMessageId={"mfa-code"} />
72
+
73
+ <SiteAlert type="notice" dismissable />
74
+ <div className="form-card__group pt-0">
75
+ <Form id="sign-in-mfa" className="mt-10" onSubmit={handleSubmit(onSubmit, onError)}>
76
+ <Field
77
+ caps={true}
78
+ name="mfaCode"
79
+ label={t("nav.signInMFA.code")}
80
+ validation={{ required: true }}
81
+ error={errors.mfaCode}
82
+ errorMessage={t("nav.signInMFA.noMFACode")}
83
+ register={register}
84
+ dataTestId="sign-in-mfa-code-field"
85
+ note={note}
86
+ />
87
+ <div className="text-center mt-10">
88
+ <Button styleType={AppearanceStyleType.primary} data-test-id="verify-and-sign-in">
89
+ {t("nav.signInMFA.signIn")}
90
+ </Button>
91
+ </div>
92
+ </Form>
93
+ </div>
94
+ </FormCard>
95
+ )
96
+ }
97
+
98
+ export { FormSignInMFACode as default, FormSignInMFACode }
@@ -0,0 +1,95 @@
1
+ import React from "react"
2
+ import {
3
+ AppearanceStyleType,
4
+ Button,
5
+ Field,
6
+ Form,
7
+ FormCard,
8
+ Icon,
9
+ t,
10
+ SiteAlert,
11
+ FormSignInErrorBox,
12
+ } from "@bloom-housing/ui-components"
13
+ import type { UseFormMethods } from "react-hook-form"
14
+ import { FormSignInNetworkError } from "./FormSignIn"
15
+ import { EnumRequestMfaCodeMfaType } from "@bloom-housing/backend-core/types"
16
+
17
+ export type FormSignInMFAProps = {
18
+ control: FormSignInMFAControl
19
+ onSubmit: (data: FormSignInMFAValues) => void
20
+ networkError: FormSignInNetworkError
21
+ }
22
+
23
+ export type FormSignInMFAControl = {
24
+ errors: UseFormMethods["errors"]
25
+ handleSubmit: UseFormMethods["handleSubmit"]
26
+ register: UseFormMethods["register"]
27
+ setValue: UseFormMethods["setValue"]
28
+ }
29
+
30
+ export type FormSignInMFAValues = {
31
+ mfaType: EnumRequestMfaCodeMfaType
32
+ }
33
+
34
+ const FormSignInMFAType = ({
35
+ onSubmit,
36
+ networkError,
37
+ control: { errors, register, handleSubmit, setValue },
38
+ }: FormSignInMFAProps) => {
39
+ const onError = () => {
40
+ window.scrollTo(0, 0)
41
+ }
42
+
43
+ return (
44
+ <FormCard>
45
+ <div className="form-card__lead text-center">
46
+ <Icon size="2xl" symbol="profile" className="form-card__header-icon" />
47
+ <h2 className="form-card__title is-borderless">
48
+ {t("nav.signInMFA.verificationChoiceMainTitle")}
49
+ </h2>
50
+ <p className="form-card__sub-title">
51
+ {t("nav.signInMFA.verificationChoiceSecondaryTitle")}
52
+ </p>
53
+ </div>
54
+ <FormSignInErrorBox errors={errors} networkError={networkError} errorMessageId={"mfa-type"} />
55
+
56
+ <SiteAlert type="notice" dismissable />
57
+ <div className="form-card__group pt-0">
58
+ <Form id="sign-in-mfa" className="mt-10" onSubmit={handleSubmit(onSubmit, onError)}>
59
+ <Field
60
+ caps={true}
61
+ name="mfaType"
62
+ label={"MFA Type"}
63
+ validation={{ required: true }}
64
+ error={errors.mfaType}
65
+ errorMessage={t("nav.signInMFA.noMFAType")}
66
+ register={register}
67
+ dataTestId="sign-in-mfaType-field"
68
+ hidden={true}
69
+ />
70
+
71
+ <div className="text-center mt-6">
72
+ <Button
73
+ styleType={AppearanceStyleType.accentCool}
74
+ data-test-id="verify-by-email"
75
+ onClick={() => setValue("mfaType", EnumRequestMfaCodeMfaType.email)}
76
+ >
77
+ {t("nav.signInMFA.verifyByEmail")}
78
+ </Button>
79
+ </div>
80
+ <div className="text-center mt-6">
81
+ <Button
82
+ styleType={AppearanceStyleType.accentCool}
83
+ data-test-id="verify-by-phone"
84
+ onClick={() => setValue("mfaType", EnumRequestMfaCodeMfaType.sms)}
85
+ >
86
+ {t("nav.signInMFA.verifyByPhone")}
87
+ </Button>
88
+ </div>
89
+ </Form>
90
+ </div>
91
+ </FormCard>
92
+ )
93
+ }
94
+
95
+ export { FormSignInMFAType as default, FormSignInMFAType }
@@ -24,7 +24,7 @@ const StackedTable = (props: StackedTableProps) => {
24
24
  const dataCell = Object.keys(dataRow).reduce((acc, item) => {
25
25
  acc[item] = (
26
26
  <div
27
- className={`md:flex md:flex-col ${
27
+ className={`md:flex md:flex-col w-1/2 md:w-full ${
28
28
  props.headersHiddenDesktop?.includes(item) && "md:hidden"
29
29
  }`}
30
30
  >
@@ -1,25 +0,0 @@
1
- import * as React from "react"
2
- import { ListingEvent } from "@bloom-housing/backend-core/types"
3
- import dayjs from "dayjs"
4
-
5
- const EventDateSection = (props: { event: ListingEvent }) => {
6
- const getRangeString = () => {
7
- const startTime = dayjs(props.event.startTime).format("hh:mma")
8
- const endTime = dayjs(props.event.endTime).format("hh:mma")
9
- return startTime === endTime ? startTime : `${startTime} - ${endTime}`
10
- }
11
- return (
12
- <>
13
- {props.event.startTime && (
14
- <p className="text text-gray-800 pb-3 flex justify-between items-center">
15
- <span className="inline-block text-tiny uppercase">
16
- {dayjs(props.event.startTime).format("MMMM D, YYYY")}
17
- </span>
18
- <span className="inline-block text-sm font-bold">{getRangeString()}</span>
19
- </p>
20
- )}
21
- </>
22
- )
23
- }
24
-
25
- export { EventDateSection as default, EventDateSection }
@@ -1,26 +0,0 @@
1
- import * as React from "react"
2
- import { ListingEvent } from "@bloom-housing/backend-core/types"
3
- import { t } from "../../../../helpers/translator"
4
- import dayjs from "dayjs"
5
-
6
- const LotteryResultsEvent = (props: { event: ListingEvent }) => {
7
- const { event } = props
8
- return (
9
- <section className="aside-block">
10
- <h4 className="text-caps-underline">{t("listings.lotteryResults.header")}</h4>
11
- <p className="text text-gray-800 pb-3 flex justify-between items-center">
12
- <span className="inline-block">{dayjs(props.event.startTime).format("MMMM D, YYYY")}</span>
13
- </p>
14
- {event.note && <p className="text text-gray-600">{event.note}</p>}
15
- {!event.note && (
16
- <p className="text-tiny text-gray-600">
17
- {t("listings.lotteryResults.completeResultsWillBePosted", {
18
- hour: dayjs(props.event.startTime).format("h a"),
19
- })}
20
- </p>
21
- )}
22
- </section>
23
- )
24
- }
25
-
26
- export { LotteryResultsEvent as default, LotteryResultsEvent }
@@ -1,27 +0,0 @@
1
- import * as React from "react"
2
- import { ListingEvent } from "@bloom-housing/backend-core/types"
3
- import { EventDateSection } from "./EventDateSection"
4
- import { t } from "../../../../helpers/translator"
5
-
6
- const OpenHouseEvent = (props: { events: ListingEvent[] }) => {
7
- return (
8
- <section className="aside-block">
9
- <h4 className="text-caps-tiny">{t("listings.openHouseEvent.header")}</h4>
10
- {props.events.map((openHouseEvent, index) => (
11
- <div key={`openHouses-${index}`}>
12
- <EventDateSection event={openHouseEvent} />
13
- {openHouseEvent.url && (
14
- <p className="text text-gray-800 pb-3">
15
- <a href={openHouseEvent.url}>
16
- {openHouseEvent.label || t("listings.openHouseEvent.seeVideo")}
17
- </a>
18
- </p>
19
- )}
20
- {openHouseEvent.note && <p className="text-tiny text-gray-600">{openHouseEvent.note}</p>}
21
- </div>
22
- ))}
23
- </section>
24
- )
25
- }
26
-
27
- export { OpenHouseEvent as default, OpenHouseEvent }
@@ -1,22 +0,0 @@
1
- import * as React from "react"
2
- import { ListingEvent } from "@bloom-housing/backend-core/types"
3
- import { t } from "../../../../helpers/translator"
4
- import { EventDateSection } from "./EventDateSection"
5
-
6
- const PublicLotteryEvent = (props: { event: ListingEvent }) => {
7
- const { event } = props
8
- return (
9
- <section className="aside-block">
10
- <h4 className="text-caps-underline">{t("listings.publicLottery.header")}</h4>
11
- <EventDateSection event={props.event} />
12
- {event.url && (
13
- <p className="text text-gray-800 pb-3">
14
- <a href={event.url}>{t("listings.publicLottery.seeVideo")}</a>
15
- </p>
16
- )}
17
- {event.note && <p className="text-tiny text-gray-600">{event.note}</p>}
18
- </section>
19
- )
20
- }
21
-
22
- export { PublicLotteryEvent as default, PublicLotteryEvent }