@bloom-housing/ui-components 4.1.2 → 4.1.3-alpha.2

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 (33) hide show
  1. package/CHANGELOG.md +360 -2
  2. package/package.json +3 -3
  3. package/scripts/duplicate-translations.ts +50 -0
  4. package/scripts/missing-translations.ts +52 -0
  5. package/scripts/reformat-strings.ts +68 -0
  6. package/scripts/unused-foreign-keys.ts +50 -0
  7. package/src/blocks/ImageCard.scss +3 -31
  8. package/src/blocks/ImageCard.tsx +41 -16
  9. package/src/forms/DOBField.tsx +3 -3
  10. package/src/forms/DateField.tsx +3 -3
  11. package/src/forms/HouseholdSizeField.tsx +1 -1
  12. package/src/forms/TimeField.tsx +1 -1
  13. package/src/global/text.scss +30 -0
  14. package/src/headers/Heading.tsx +17 -6
  15. package/src/helpers/useMutate.ts +11 -1
  16. package/src/locales/es.json +629 -900
  17. package/src/locales/general.json +913 -1759
  18. package/src/locales/tl.json +1 -3
  19. package/src/locales/vi.json +631 -901
  20. package/src/locales/zh.json +631 -901
  21. package/src/navigation/FooterNav.scss +1 -2
  22. package/src/navigation/FooterNav.tsx +2 -3
  23. package/src/notifications/ApplicationStatus.tsx +22 -15
  24. package/src/page_components/forgot-password/FormForgotPassword.tsx +3 -9
  25. package/src/page_components/listing/ListingCard.scss +9 -11
  26. package/src/page_components/listing/ListingCard.tsx +131 -47
  27. package/src/page_components/sign-in/FormSignIn.tsx +4 -9
  28. package/src/page_components/sign-in/FormSignInAddPhone.tsx +3 -3
  29. package/src/page_components/sign-in/FormSignInErrorBox.tsx +21 -9
  30. package/src/page_components/sign-in/FormSignInMFACode.tsx +8 -3
  31. package/src/page_components/sign-in/FormSignInMFAType.tsx +7 -3
  32. package/src/locales/general_OLD.json +0 -868
  33. package/src/locales/missing-translations.ts +0 -72
@@ -30,8 +30,7 @@
30
30
 
31
31
  .footer-copyright {
32
32
  @screen lg {
33
- @apply w-6/12;
34
- @apply text-left;
33
+ @apply w-full;
35
34
  }
36
35
  }
37
36
 
@@ -2,7 +2,7 @@ import * as React from "react"
2
2
  import "./FooterNav.scss"
3
3
 
4
4
  export interface FooterNavProps {
5
- children: React.ReactNode
5
+ children?: React.ReactNode
6
6
  copyright: string
7
7
  }
8
8
 
@@ -10,8 +10,7 @@ const FooterNav = (props: FooterNavProps) => (
10
10
  <section className="footer-sock">
11
11
  <div className="footer-sock__inner">
12
12
  <p className="footer-copyright">{props.copyright}</p>
13
-
14
- <nav className="footer-nav">{props.children}</nav>
13
+ {props.children && <nav className="footer-nav">{props.children}</nav>}
15
14
  </div>
16
15
  </section>
17
16
  )
@@ -5,30 +5,37 @@ import "./ApplicationStatus.scss"
5
5
 
6
6
  export interface ApplicationStatusProps {
7
7
  content: string
8
- subContent?: string
8
+ iconColor?: string
9
+ iconType?: IconTypes
9
10
  status?: ApplicationStatusType
11
+ subContent?: string
10
12
  vivid?: boolean
11
13
  withIcon?: boolean
12
- iconType?: IconTypes
13
14
  }
14
15
 
15
16
  const ApplicationStatus = (props: ApplicationStatusProps) => {
16
17
  let bgColor = ""
18
+ const {
19
+ content,
20
+ iconColor,
21
+ iconType = "clock",
22
+ status = ApplicationStatusType.Open,
23
+ subContent,
24
+ vivid,
25
+ withIcon = true,
26
+ } = props
27
+
17
28
  // determine styling
18
- const vivid = props.vivid || false
19
29
  let textColor = vivid ? "text-white" : "text-gray-800"
20
30
  const textSize = vivid ? "text-xs" : "text-sm"
21
31
 
22
- const status = props.status || ApplicationStatusType.Open
23
- const content = props.content
24
- const withIcon = props.withIcon ?? true
25
- const iconType = props.iconType ?? "clock"
26
-
27
- let icon
28
-
29
- if (withIcon) {
30
- icon = <Icon size="medium" symbol={iconType} fill={vivid ? IconFillColors.white : undefined} />
31
- }
32
+ const icon = withIcon && (
33
+ <Icon
34
+ size="medium"
35
+ symbol={iconType}
36
+ fill={iconColor || (vivid ? IconFillColors.white : undefined)}
37
+ />
38
+ )
32
39
 
33
40
  switch (status) {
34
41
  case ApplicationStatusType.Open:
@@ -54,10 +61,10 @@ const ApplicationStatus = (props: ApplicationStatusProps) => {
54
61
  {icon}
55
62
  <span>
56
63
  {content}
57
- {props.subContent && (
64
+ {subContent && (
58
65
  <>
59
66
  <br />
60
- {props.subContent}
67
+ {subContent}
61
68
  </>
62
69
  )}
63
70
  </span>
@@ -13,6 +13,7 @@ import {
13
13
  ErrorMessage,
14
14
  emailRegex,
15
15
  } from "@bloom-housing/ui-components"
16
+ import { NetworkErrorReset, NetworkStatusContent } from "@bloom-housing/shared-helpers"
16
17
  import { NavigationContext } from "../../config/NavigationContext"
17
18
  import type { UseFormMethods } from "react-hook-form"
18
19
 
@@ -22,15 +23,8 @@ export type FormForgotPasswordProps = {
22
23
  networkError: FormForgotPasswordNetworkError
23
24
  }
24
25
 
25
- export type NetworkErrorReset = () => void
26
-
27
- export type NetworkErrorValue = {
28
- title: string
29
- content: string
30
- } | null
31
-
32
26
  export type FormForgotPasswordNetworkError = {
33
- error: NetworkErrorValue
27
+ error: NetworkStatusContent
34
28
  reset: NetworkErrorReset
35
29
  }
36
30
 
@@ -75,7 +69,7 @@ const FormForgotPassword = ({
75
69
  </AlertBox>
76
70
 
77
71
  <AlertNotice title="" type="alert" inverted>
78
- {networkError.error.content}
72
+ {networkError.error.description}
79
73
  </AlertNotice>
80
74
  </ErrorMessage>
81
75
  )}
@@ -34,19 +34,17 @@
34
34
  }
35
35
  }
36
36
 
37
- .listings-row_title {
38
- @apply font-alt-sans;
39
- @apply font-semibold;
40
- @apply text-gray-900;
41
- @apply text-base;
42
- @apply mb-2;
37
+ .listings-row_table {
38
+ @apply mb-4;
43
39
  }
44
40
 
45
- .listings-row_subtitle {
46
- @apply font-alt-sans;
47
- @apply text-gray-800;
48
- @apply text-sm;
49
- @apply mb-3;
41
+ .listings-row_footer {
42
+ @apply flex;
43
+ @apply justify-end;
44
+ @apply w-full;
45
+ a:not(:first-child) {
46
+ @apply ml-1;
47
+ }
50
48
  }
51
49
 
52
50
  .listings-row_table {
@@ -1,70 +1,133 @@
1
1
  import * as React from "react"
2
- import { ImageCard, ImageCardProps } from "../../blocks/ImageCard"
2
+ import { ImageCard, ImageCardProps, ImageTag } from "../../blocks/ImageCard"
3
3
  import { LinkButton } from "../../actions/LinkButton"
4
4
  import { StackedTable, StackedTableProps } from "../../tables/StackedTable"
5
-
6
- import { t } from "../../helpers/translator"
7
- import "./ListingCard.scss"
8
5
  import { StandardTable, StandardTableProps } from "../../tables/StandardTable"
6
+ import { Heading, HeaderType } from "../../headers/Heading"
7
+ import { Tag } from "../../text/Tag"
8
+ import { AppearanceStyleType } from "../../global/AppearanceTypes"
9
+ import { Icon, IconFillColors } from "../../icons/Icon"
10
+ import "./ListingCard.scss"
9
11
 
10
12
  interface ListingCardTableProps extends StandardTableProps, StackedTableProps {}
11
13
 
12
- export interface ListingCardHeaderProps {
13
- tableHeader?: string
14
- tableHeaderClass?: string
15
- tableSubHeader?: string
16
- tableSubHeaderClass?: string
17
- stackedTable?: boolean
14
+ export interface CardHeader {
15
+ customClass?: string
16
+ text: string
17
+ }
18
+
19
+ export interface FooterButton {
20
+ href: string
21
+ text: string
22
+ }
23
+
24
+ export interface ListingCardContentProps {
25
+ contentHeader?: CardHeader
26
+ contentSubheader?: CardHeader
27
+ tableHeader?: CardHeader
28
+ tableSubheader?: CardHeader
18
29
  }
19
30
  export interface ListingCardProps {
20
- imageCardProps: ImageCardProps
31
+ cardTags?: ImageTag[]
21
32
  children?: React.ReactElement
22
- seeDetailsLink?: string
23
- tableHeaderProps?: ListingCardHeaderProps
33
+ contentProps?: ListingCardContentProps
34
+ footerButtons?: FooterButton[]
35
+ footerContainerClass?: string
36
+ footerContent?: React.ReactNode
37
+ imageCardProps: ImageCardProps
38
+ stackedTable?: boolean
24
39
  tableProps?: ListingCardTableProps
25
- detailsLinkClass?: string
26
40
  }
27
41
 
42
+ /**
43
+ * @component ListingCard
44
+ *
45
+ * A component that renders an image with optional status bars below it,
46
+ * and a content section associated with the image which can include titles, a table, and custom content
47
+ *
48
+ * @prop cardTags -A list of tags to be rendered below the content header, a Tag component is rendered for each
49
+ * @prop children - Custom content rendered in the content section above the table
50
+ * @prop footerButtons - A list of buttons to render in the footer of the content section
51
+ * @prop footerContent - Custom content rendered below the content table
52
+ * @prop footerContainerClass - A class name applied to the footer container of the content section
53
+ * @prop imageCardProps - Prop interface for the ImageCard component
54
+ * @prop stackedTable - Toggles on the StackedTable component in place of the default StandardTable component - they are functionally equivalent with differing UIs
55
+ * @prop contentProps - An object containing fields that render optional headers above the content section's table
56
+ * @prop tableProps - Prop interface for the StandardTable and StackedTable components
57
+ *
58
+ */
28
59
  const ListingCard = (props: ListingCardProps) => {
29
- const { imageCardProps, tableProps, detailsLinkClass, tableHeaderProps, children } = props
60
+ const {
61
+ cardTags,
62
+ children,
63
+ footerButtons,
64
+ footerContent,
65
+ footerContainerClass,
66
+ imageCardProps,
67
+ stackedTable,
68
+ contentProps,
69
+ tableProps,
70
+ } = props
30
71
 
31
- const tableHeader = () => {
32
- return (
33
- <h3
34
- className={`listings-row_title ${
35
- tableHeaderProps?.tableHeaderClass && tableHeaderProps?.tableHeaderClass
36
- }`}
37
- >
38
- {tableHeaderProps?.tableHeader}
39
- </h3>
40
- )
72
+ const getHeader = (
73
+ header: CardHeader | undefined,
74
+ priority: number,
75
+ style?: HeaderType,
76
+ customClass?: string
77
+ ) => {
78
+ if (header && header.text) {
79
+ return (
80
+ <Heading priority={priority} style={style} className={customClass}>
81
+ {header.text}
82
+ </Heading>
83
+ )
84
+ } else {
85
+ return <></>
86
+ }
41
87
  }
42
88
 
43
- const tableSubHeader = () => {
89
+ const getContentHeader = () => {
44
90
  return (
45
- <h4
46
- className={`listings-row_subtitle ${
47
- tableHeaderProps?.tableSubHeaderClass && tableHeaderProps?.tableSubHeaderClass
48
- }`}
49
- >
50
- {tableHeaderProps?.tableSubHeader}
51
- </h4>
91
+ <>
92
+ {getHeader(contentProps?.contentHeader, 2, "cardHeader", "order-1")}
93
+ {getHeader(contentProps?.contentSubheader, 3, "cardSubheader", "order-2")}
94
+ {cardTags && cardTags?.length > 0 && (
95
+ <div className={"inline-flex flex-wrap justify-start w-full"}>
96
+ {cardTags?.map((cardTag, index) => {
97
+ return (
98
+ <Tag styleType={AppearanceStyleType.warning} className={"mr-2 mb-2"} key={index}>
99
+ {cardTag.iconType && (
100
+ <Icon
101
+ size={"medium"}
102
+ symbol={cardTag.iconType}
103
+ fill={cardTag.iconColor ?? IconFillColors.primary}
104
+ className={"mr-2"}
105
+ />
106
+ )}
107
+ {cardTag.text}
108
+ </Tag>
109
+ )
110
+ })}
111
+ </div>
112
+ )}
113
+ </>
52
114
  )
53
115
  }
54
116
 
55
- return (
56
- <article className="listings-row" data-test-id={"listing-card-component"}>
57
- <div className="listings-row_figure">
58
- <ImageCard {...imageCardProps} />
59
- </div>
60
- <div className="listings-row_content">
61
- {tableHeaderProps?.tableHeader && tableHeader()}
62
- {tableHeaderProps?.tableSubHeader && tableSubHeader()}
117
+ const getContent = () => {
118
+ return (
119
+ <>
63
120
  <div className="listings-row_table">
121
+ {(contentProps?.tableHeader?.text || contentProps?.tableSubheader?.text) &&
122
+ (contentProps.contentHeader?.text || contentProps?.contentSubheader?.text) && (
123
+ <hr className={"mb-2"} />
124
+ )}
125
+ {getHeader(contentProps?.tableHeader, 4, "tableHeader")}
126
+ {getHeader(contentProps?.tableSubheader, 5, "tableSubheader")}
64
127
  {children && children}
65
128
  {tableProps && (tableProps.data || tableProps.stackedData) && (
66
129
  <>
67
- {tableHeaderProps?.stackedTable ? (
130
+ {stackedTable ? (
68
131
  <StackedTable {...(tableProps as StackedTableProps)} />
69
132
  ) : (
70
133
  <StandardTable {...(tableProps as StandardTableProps)} />
@@ -72,11 +135,32 @@ const ListingCard = (props: ListingCardProps) => {
72
135
  </>
73
136
  )}
74
137
  </div>
75
- {props.seeDetailsLink && (
76
- <LinkButton className={detailsLinkClass} href={props.seeDetailsLink}>
77
- {t("t.seeDetails")}
78
- </LinkButton>
79
- )}
138
+ <div className={"flex flex-col"}>
139
+ {footerContent && footerContent}
140
+ {footerButtons && footerButtons?.length > 0 && (
141
+ <div className={footerContainerClass ?? "listings-row_footer"}>
142
+ {footerButtons?.map((footerButton, index) => {
143
+ return (
144
+ <LinkButton href={footerButton.href} key={index}>
145
+ {footerButton.text}
146
+ </LinkButton>
147
+ )
148
+ })}
149
+ </div>
150
+ )}
151
+ </div>
152
+ </>
153
+ )
154
+ }
155
+
156
+ return (
157
+ <article className="listings-row" data-test-id={"listing-card-component"}>
158
+ <div className="listings-row_figure">
159
+ <ImageCard {...imageCardProps} />
160
+ </div>
161
+ <div className="listings-row_content">
162
+ <div>{getContentHeader()}</div>
163
+ {getContent()}
80
164
  </div>
81
165
  </article>
82
166
  )
@@ -12,22 +12,17 @@ import {
12
12
  } from "@bloom-housing/ui-components"
13
13
  import type { UseFormMethods } from "react-hook-form"
14
14
  import { NavigationContext } from "../../config/NavigationContext"
15
- import type { NetworkErrorReset, NetworkErrorValue } from "../forgot-password/FormForgotPassword"
15
+ import { NetworkStatus } from "@bloom-housing/shared-helpers"
16
16
 
17
17
  export type NetworkErrorDetermineError = (status: number, error: Error) => void
18
18
 
19
19
  export type FormSignInProps = {
20
20
  control: FormSignInControl
21
21
  onSubmit: (data: FormSignInValues) => void
22
- networkError: FormSignInNetworkError
22
+ networkStatus: NetworkStatus
23
23
  showRegisterBtn?: boolean
24
24
  }
25
25
 
26
- export type FormSignInNetworkError = {
27
- error: NetworkErrorValue
28
- reset: NetworkErrorReset
29
- }
30
-
31
26
  export type FormSignInControl = {
32
27
  errors: UseFormMethods["errors"]
33
28
  handleSubmit: UseFormMethods["handleSubmit"]
@@ -41,7 +36,7 @@ export type FormSignInValues = {
41
36
 
42
37
  const FormSignIn = ({
43
38
  onSubmit,
44
- networkError,
39
+ networkStatus,
45
40
  showRegisterBtn,
46
41
  control: { errors, register, handleSubmit },
47
42
  }: FormSignInProps) => {
@@ -58,7 +53,7 @@ const FormSignIn = ({
58
53
  </div>
59
54
  <FormSignInErrorBox
60
55
  errors={errors}
61
- networkError={networkError}
56
+ networkStatus={networkStatus}
62
57
  errorMessageId={"main-sign-in"}
63
58
  />
64
59
  <div className="form-card__group pt-0">
@@ -11,12 +11,12 @@ import {
11
11
  FormSignInErrorBox,
12
12
  } from "@bloom-housing/ui-components"
13
13
  import type { UseFormMethods } from "react-hook-form"
14
- import { FormSignInNetworkError } from "./FormSignIn"
14
+ import { NetworkStatus } from "@bloom-housing/shared-helpers"
15
15
 
16
16
  export type FormSignInAddPhoneProps = {
17
17
  control: FormSignInAddPhoneControl
18
18
  onSubmit: (data: FormSignInAddPhoneValues) => void
19
- networkError: FormSignInNetworkError
19
+ networkError: NetworkStatus
20
20
  phoneNumber: string
21
21
  }
22
22
 
@@ -49,7 +49,7 @@ const FormSignInAddPhone = ({
49
49
  </div>
50
50
  <FormSignInErrorBox
51
51
  errors={errors}
52
- networkError={networkError}
52
+ networkStatus={networkError}
53
53
  errorMessageId={"add-phone"}
54
54
  />
55
55
 
@@ -1,11 +1,11 @@
1
1
  import React from "react"
2
2
  import { t, AlertBox, SiteAlert, AlertNotice, ErrorMessage } from "@bloom-housing/ui-components"
3
3
  import type { UseFormMethods } from "react-hook-form"
4
- import { FormSignInNetworkError } from "./FormSignIn"
4
+ import { NetworkStatus } from "@bloom-housing/shared-helpers"
5
5
 
6
6
  export type FormSignInErrorBoxProps = {
7
7
  errors: FormSignInErrorBoxControl["errors"]
8
- networkError: FormSignInNetworkError
8
+ networkStatus: NetworkStatus
9
9
  errorMessageId: string
10
10
  }
11
11
 
@@ -14,27 +14,39 @@ export type FormSignInErrorBoxControl = {
14
14
  control: UseFormMethods["control"]
15
15
  }
16
16
 
17
- const FormSignInErrorBox = ({ networkError, errors, errorMessageId }: FormSignInErrorBoxProps) => {
17
+ const FormSignInErrorBox = ({ networkStatus, errors, errorMessageId }: FormSignInErrorBoxProps) => {
18
18
  return (
19
19
  <div className="border-b">
20
- {Object.entries(errors).length > 0 && !networkError.error && (
20
+ {Object.entries(errors).length > 0 && !networkStatus.content && (
21
21
  <AlertBox type="alert" inverted closeable>
22
22
  {errors.authentication ? errors.authentication.message : t("errors.errorsToResolve")}
23
23
  </AlertBox>
24
24
  )}
25
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}
26
+ {networkStatus.content?.error && Object.entries(errors).length === 0 && (
27
+ <ErrorMessage id={`form-sign-in-${errorMessageId}-error`} error={!!networkStatus.content}>
28
+ <AlertBox type={"alert"} inverted onClose={() => networkStatus.reset()}>
29
+ {networkStatus.content.title}
30
30
  </AlertBox>
31
31
 
32
32
  <AlertNotice title="" type="alert" inverted>
33
- {networkError.error.content}
33
+ {networkStatus.content.description}
34
34
  </AlertNotice>
35
35
  </ErrorMessage>
36
36
  )}
37
37
 
38
+ {networkStatus.type === "success" && (
39
+ <>
40
+ <AlertBox type="success" inverted onClose={() => networkStatus.reset()}>
41
+ {networkStatus.content?.title}
42
+ </AlertBox>
43
+
44
+ <AlertNotice title="" type="success" inverted>
45
+ {networkStatus.content?.description}
46
+ </AlertNotice>
47
+ </>
48
+ )}
49
+
38
50
  <SiteAlert type="notice" dismissable />
39
51
  </div>
40
52
  )
@@ -10,13 +10,14 @@ import {
10
10
  SiteAlert,
11
11
  FormSignInErrorBox,
12
12
  } from "@bloom-housing/ui-components"
13
- import { FormSignInNetworkError, FormSignInControl } from "./FormSignIn"
13
+ import { NetworkStatus } from "@bloom-housing/shared-helpers"
14
+ import { FormSignInControl } from "./FormSignIn"
14
15
  import { EnumRequestMfaCodeMfaType } from "@bloom-housing/backend-core/types"
15
16
 
16
17
  export type FormSignInMFACodeProps = {
17
18
  control: FormSignInControl
18
19
  onSubmit: (data: FormSignInMFACodeValues) => void
19
- networkError: FormSignInNetworkError
20
+ networkError: NetworkStatus
20
21
  mfaType: EnumRequestMfaCodeMfaType
21
22
  allowPhoneNumberEdit: boolean
22
23
  phoneNumber: string
@@ -68,7 +69,11 @@ const FormSignInMFACode = ({
68
69
  : t("nav.signInMFA.haveSentCodeToEmail")}
69
70
  </p>
70
71
  </div>
71
- <FormSignInErrorBox errors={errors} networkError={networkError} errorMessageId={"mfa-code"} />
72
+ <FormSignInErrorBox
73
+ errors={errors}
74
+ networkStatus={networkError}
75
+ errorMessageId={"mfa-code"}
76
+ />
72
77
 
73
78
  <SiteAlert type="notice" dismissable />
74
79
  <div className="form-card__group pt-0">
@@ -11,13 +11,13 @@ import {
11
11
  FormSignInErrorBox,
12
12
  } from "@bloom-housing/ui-components"
13
13
  import type { UseFormMethods } from "react-hook-form"
14
- import { FormSignInNetworkError } from "./FormSignIn"
14
+ import { NetworkStatus } from "@bloom-housing/shared-helpers"
15
15
  import { EnumRequestMfaCodeMfaType } from "@bloom-housing/backend-core/types"
16
16
 
17
17
  export type FormSignInMFAProps = {
18
18
  control: FormSignInMFAControl
19
19
  onSubmit: (data: FormSignInMFAValues) => void
20
- networkError: FormSignInNetworkError
20
+ networkError: NetworkStatus
21
21
  }
22
22
 
23
23
  export type FormSignInMFAControl = {
@@ -51,7 +51,11 @@ const FormSignInMFAType = ({
51
51
  {t("nav.signInMFA.verificationChoiceSecondaryTitle")}
52
52
  </p>
53
53
  </div>
54
- <FormSignInErrorBox errors={errors} networkError={networkError} errorMessageId={"mfa-type"} />
54
+ <FormSignInErrorBox
55
+ errors={errors}
56
+ networkStatus={networkError}
57
+ errorMessageId={"mfa-type"}
58
+ />
55
59
 
56
60
  <SiteAlert type="notice" dismissable />
57
61
  <div className="form-card__group pt-0">