@bloom-housing/ui-components 2.0.0-pre-tailwind
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.
- package/.jest/setup-tests.js +24 -0
- package/CHANGELOG.md +20 -0
- package/README.md +195 -0
- package/index.ts +148 -0
- package/jest.config.js +41 -0
- package/package.json +98 -0
- package/public/images/alameda-logo-white.svg +1 -0
- package/public/images/arrow-down.png +0 -0
- package/public/images/arrow-down.svg +1 -0
- package/public/images/check.png +0 -0
- package/public/images/check.svg +11 -0
- package/public/images/eho-logo-white.svg +1 -0
- package/public/images/eho-logo.svg +1 -0
- package/public/images/logo_glyph.svg +11 -0
- package/src/actions/Button.scss +157 -0
- package/src/actions/Button.tsx +80 -0
- package/src/actions/ExpandableContent.tsx +29 -0
- package/src/actions/ExpandableText.scss +18 -0
- package/src/actions/ExpandableText.tsx +52 -0
- package/src/actions/LinkButton.tsx +30 -0
- package/src/actions/LocalizedLink.tsx +11 -0
- package/src/authentication/AuthContext.ts +327 -0
- package/src/authentication/RequireLogin.tsx +62 -0
- package/src/authentication/index.ts +5 -0
- package/src/authentication/timeout.tsx +127 -0
- package/src/authentication/token.ts +17 -0
- package/src/authentication/useRequireLoggedInUser.ts +19 -0
- package/src/blocks/ActionBlock.scss +108 -0
- package/src/blocks/ActionBlock.tsx +51 -0
- package/src/blocks/AppStatusItem.scss +140 -0
- package/src/blocks/AppStatusItem.tsx +75 -0
- package/src/blocks/DashBlock.tsx +42 -0
- package/src/blocks/DashBlocks.scss +56 -0
- package/src/blocks/DashBlocks.tsx +7 -0
- package/src/blocks/FormCard.scss +201 -0
- package/src/blocks/FormCard.tsx +29 -0
- package/src/blocks/HousingCounselor.tsx +51 -0
- package/src/blocks/ImageCard.scss +91 -0
- package/src/blocks/ImageCard.tsx +77 -0
- package/src/blocks/InfoCard.scss +42 -0
- package/src/blocks/InfoCard.tsx +44 -0
- package/src/blocks/StatusBar.scss +30 -0
- package/src/blocks/StatusBar.tsx +31 -0
- package/src/blocks/ViewItem.scss +59 -0
- package/src/blocks/ViewItem.tsx +32 -0
- package/src/config/ConfigContext.tsx +36 -0
- package/src/config/NavigationContext.tsx +54 -0
- package/src/config/index.ts +2 -0
- package/src/footers/ExygyFooter.tsx +12 -0
- package/src/footers/SiteFooter.scss +28 -0
- package/src/footers/SiteFooter.tsx +10 -0
- package/src/forms/CloudinaryUpload.ts +50 -0
- package/src/forms/DOBField.tsx +132 -0
- package/src/forms/DateField.tsx +120 -0
- package/src/forms/Dropzone.scss +17 -0
- package/src/forms/Dropzone.tsx +67 -0
- package/src/forms/Field.tsx +115 -0
- package/src/forms/FieldGroup.tsx +82 -0
- package/src/forms/Form.tsx +22 -0
- package/src/forms/HouseholdMemberForm.tsx +41 -0
- package/src/forms/HouseholdSizeField.tsx +74 -0
- package/src/forms/PhoneField.tsx +69 -0
- package/src/forms/PhoneMask.tsx +24 -0
- package/src/forms/Select.tsx +80 -0
- package/src/forms/Textarea.scss +40 -0
- package/src/forms/Textarea.tsx +64 -0
- package/src/forms/TimeField.tsx +176 -0
- package/src/global/AppearanceTypes.ts +46 -0
- package/src/global/ApplicationStatusType.ts +6 -0
- package/src/global/accordion.scss +4 -0
- package/src/global/blocks.scss +137 -0
- package/src/global/custom_counter.scss +50 -0
- package/src/global/forms.scss +362 -0
- package/src/global/headers.scss +89 -0
- package/src/global/homepage.scss +8 -0
- package/src/global/index.scss +72 -0
- package/src/global/lists.scss +21 -0
- package/src/global/markdown.scss +33 -0
- package/src/global/mixins.scss +175 -0
- package/src/global/navbar.scss +280 -0
- package/src/global/print.scss +59 -0
- package/src/global/tables.scss +197 -0
- package/src/global/text.scss +141 -0
- package/src/global/vendor/AgPagination.tsx +133 -0
- package/src/global/vendor/_setup_bulma.scss +31 -0
- package/src/global/vendor/ag_grid.scss +140 -0
- package/src/headers/Hero.scss +56 -0
- package/src/headers/Hero.tsx +76 -0
- package/src/headers/PageHeader.scss +31 -0
- package/src/headers/PageHeader.tsx +39 -0
- package/src/headers/SiteHeader.tsx +136 -0
- package/src/helpers/address.tsx +46 -0
- package/src/helpers/blankApplication.ts +108 -0
- package/src/helpers/capitalize.tsx +7 -0
- package/src/helpers/dateToString.ts +11 -0
- package/src/helpers/debounce.ts +12 -0
- package/src/helpers/formOptions.tsx +229 -0
- package/src/helpers/formatYesNoLabel.ts +9 -0
- package/src/helpers/getTranslationWithArguments.ts +14 -0
- package/src/helpers/links.ts +7 -0
- package/src/helpers/localeRoute.tsx +13 -0
- package/src/helpers/mergeDeep.ts +12 -0
- package/src/helpers/nextjs.ts +7 -0
- package/src/helpers/numberOrdinal.ts +17 -0
- package/src/helpers/occupancyFormatting.tsx +46 -0
- package/src/helpers/pdfs.ts +19 -0
- package/src/helpers/photos.ts +19 -0
- package/src/helpers/preferences.tsx +426 -0
- package/src/helpers/resolveObject.ts +5 -0
- package/src/helpers/state.tsx +7 -0
- package/src/helpers/tableSummaries.tsx +80 -0
- package/src/helpers/translator.tsx +37 -0
- package/src/helpers/useKeyPress.ts +17 -0
- package/src/helpers/useMutate.ts +40 -0
- package/src/helpers/useOutsideClick.ts +25 -0
- package/src/helpers/validators.ts +3 -0
- package/src/icons/HeaderBadge.scss +29 -0
- package/src/icons/HeaderBadge.tsx +38 -0
- package/src/icons/Icon.scss +76 -0
- package/src/icons/Icon.tsx +145 -0
- package/src/icons/Icons.tsx +556 -0
- package/src/lists/PreferencesList.scss +72 -0
- package/src/lists/PreferencesList.tsx +60 -0
- package/src/locales/es.json +745 -0
- package/src/locales/general.json +1307 -0
- package/src/locales/general_OLD.json +868 -0
- package/src/locales/vi.json +745 -0
- package/src/locales/zh.json +745 -0
- package/src/navigation/Breadcrumbs.scss +25 -0
- package/src/navigation/Breadcrumbs.tsx +27 -0
- package/src/navigation/FooterNav.scss +47 -0
- package/src/navigation/FooterNav.tsx +19 -0
- package/src/navigation/LanguageNav.scss +32 -0
- package/src/navigation/LanguageNav.tsx +53 -0
- package/src/navigation/ProgressNav.scss +102 -0
- package/src/navigation/ProgressNav.tsx +50 -0
- package/src/navigation/TabNav.scss +38 -0
- package/src/navigation/TabNav.tsx +69 -0
- package/src/navigation/Tabs.scss +65 -0
- package/src/navigation/Tabs.tsx +93 -0
- package/src/navigation/UserNav.tsx +37 -0
- package/src/notifications/AlertBox.scss +78 -0
- package/src/notifications/AlertBox.tsx +79 -0
- package/src/notifications/AlertNotice.scss +58 -0
- package/src/notifications/AlertNotice.tsx +37 -0
- package/src/notifications/ApplicationStatus.scss +10 -0
- package/src/notifications/ApplicationStatus.tsx +64 -0
- package/src/notifications/ErrorMessage.tsx +15 -0
- package/src/notifications/SiteAlert.tsx +54 -0
- package/src/notifications/StatusAside.scss +11 -0
- package/src/notifications/StatusAside.tsx +25 -0
- package/src/notifications/StatusMessage.scss +25 -0
- package/src/notifications/StatusMessage.tsx +59 -0
- package/src/notifications/alertTypes.ts +7 -0
- package/src/notifications/index.ts +4 -0
- package/src/overlays/Drawer.scss +105 -0
- package/src/overlays/Drawer.tsx +51 -0
- package/src/overlays/LoadingOverlay.scss +25 -0
- package/src/overlays/LoadingOverlay.tsx +29 -0
- package/src/overlays/Modal.scss +55 -0
- package/src/overlays/Modal.tsx +61 -0
- package/src/overlays/Overlay.scss +50 -0
- package/src/overlays/Overlay.tsx +100 -0
- package/src/page_components/listing/AdditionalFees.tsx +56 -0
- package/src/page_components/listing/ListingCard.scss +47 -0
- package/src/page_components/listing/ListingCard.tsx +34 -0
- package/src/page_components/listing/ListingDetailHeader.tsx +25 -0
- package/src/page_components/listing/ListingDetails.tsx +29 -0
- package/src/page_components/listing/ListingMap.scss +36 -0
- package/src/page_components/listing/ListingMap.tsx +138 -0
- package/src/page_components/listing/ListingsGroup.scss +65 -0
- package/src/page_components/listing/ListingsGroup.tsx +49 -0
- package/src/page_components/listing/UnitTables.tsx +111 -0
- package/src/page_components/listing/listing_sidebar/ApplicationSection.tsx +49 -0
- package/src/page_components/listing/listing_sidebar/Apply.tsx +225 -0
- package/src/page_components/listing/listing_sidebar/LeasingAgent.tsx +77 -0
- package/src/page_components/listing/listing_sidebar/ListingUpdated.tsx +20 -0
- package/src/page_components/listing/listing_sidebar/ReferralApplication.tsx +28 -0
- package/src/page_components/listing/listing_sidebar/SidebarAddress.tsx +56 -0
- package/src/page_components/listing/listing_sidebar/Waitlist.tsx +94 -0
- package/src/page_components/listing/listing_sidebar/WhatToExpect.tsx +22 -0
- package/src/page_components/listing/listing_sidebar/events/DownloadLotteryResults.tsx +34 -0
- package/src/page_components/listing/listing_sidebar/events/EventDateSection.tsx +24 -0
- package/src/page_components/listing/listing_sidebar/events/LotteryResultsEvent.tsx +26 -0
- package/src/page_components/listing/listing_sidebar/events/OpenHouseEvent.tsx +27 -0
- package/src/page_components/listing/listing_sidebar/events/PublicLotteryEvent.tsx +22 -0
- package/src/prototypes/AppCard.scss +64 -0
- package/src/prototypes/Back.scss +19 -0
- package/src/prototypes/ButtonGroup.scss +6 -0
- package/src/prototypes/ButtonPager.scss +22 -0
- package/src/prototypes/FieldSection.scss +35 -0
- package/src/prototypes/FieldSection.tsx +31 -0
- package/src/prototypes/GridItem.tsx +15 -0
- package/src/prototypes/SideNav.scss +32 -0
- package/src/prototypes/SideNav.tsx +14 -0
- package/src/prototypes/SummaryCard.scss +34 -0
- package/src/sections/ContentSection.scss +15 -0
- package/src/sections/ContentSection.tsx +25 -0
- package/src/sections/FooterSection.scss +6 -0
- package/src/sections/FooterSection.tsx +16 -0
- package/src/sections/GridSection.scss +72 -0
- package/src/sections/GridSection.tsx +82 -0
- package/src/sections/InfoCardGrid.scss +45 -0
- package/src/sections/InfoCardGrid.tsx +20 -0
- package/src/sections/ListSection.scss +7 -0
- package/src/sections/ListSection.tsx +23 -0
- package/src/sections/MarkdownSection.scss +13 -0
- package/src/sections/MarkdownSection.tsx +21 -0
- package/src/sections/ResponsiveContentList.tsx +67 -0
- package/src/sections/ResponsiveWrappers.tsx +23 -0
- package/src/tables/GroupedTable.tsx +86 -0
- package/src/tables/MinimalTable.tsx +32 -0
- package/src/tables/ResponsiveTable.tsx +24 -0
- package/src/tables/StandardTable.tsx +229 -0
- package/src/text/Description.scss +52 -0
- package/src/text/Description.tsx +24 -0
- package/src/text/Message.scss +16 -0
- package/src/text/Message.tsx +16 -0
- package/src/text/Tag.scss +94 -0
- package/src/text/Tag.tsx +22 -0
- package/tailwind.config.js +128 -0
- package/tailwind.tosass.js +29 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { t } from "./translator"
|
|
2
|
+
|
|
3
|
+
export const lRoute = (routeString: string) => {
|
|
4
|
+
if (routeString.startsWith("http")) return routeString
|
|
5
|
+
|
|
6
|
+
let routePrefix = t("config.routePrefix")
|
|
7
|
+
if (routePrefix == "config.routePrefix" || routePrefix == "") {
|
|
8
|
+
routePrefix = "" // no prefix needed for default routes
|
|
9
|
+
} else {
|
|
10
|
+
routePrefix = "/" + routePrefix
|
|
11
|
+
}
|
|
12
|
+
return `${routePrefix}${routeString}`
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const mergeDeep = (target: any, source: any) => {
|
|
2
|
+
// Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
|
|
3
|
+
for (const key of Object.keys(source)) {
|
|
4
|
+
if (source[key] instanceof Object) {
|
|
5
|
+
Object.assign(source[key], mergeDeep(target[key], source[key]))
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Join `target` and modified `source`
|
|
10
|
+
Object.assign(target || {}, source)
|
|
11
|
+
return target
|
|
12
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const numberOrdinal = (num: number): string => {
|
|
2
|
+
const standardSuffix = "th"
|
|
3
|
+
const oneToThreeSuffixes = ["st", "nd", "rd"]
|
|
4
|
+
|
|
5
|
+
const numStr = num.toString()
|
|
6
|
+
const lastTwoDigits = parseInt(numStr.slice(-2), 10)
|
|
7
|
+
const lastDigit = parseInt(numStr.slice(-1), 10)
|
|
8
|
+
|
|
9
|
+
let suffix = ""
|
|
10
|
+
if (lastDigit >= 1 && lastDigit <= 3 && !(lastTwoDigits >= 11 && lastTwoDigits <= 13)) {
|
|
11
|
+
suffix = oneToThreeSuffixes[lastDigit - 1]
|
|
12
|
+
} else {
|
|
13
|
+
suffix = standardSuffix
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return `${num}${suffix}`
|
|
17
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { t } from "./translator"
|
|
3
|
+
import { Listing } from "@bloom-housing/backend-core/types"
|
|
4
|
+
|
|
5
|
+
export const occupancyTable = (listing: Listing) => {
|
|
6
|
+
let occupancyData = [] as any
|
|
7
|
+
if (listing.unitsSummarized && listing.unitsSummarized.byUnitType) {
|
|
8
|
+
occupancyData = listing.unitsSummarized.byUnitType.map((unitSummary) => {
|
|
9
|
+
let occupancy = ""
|
|
10
|
+
|
|
11
|
+
if (unitSummary.occupancyRange.max == null) {
|
|
12
|
+
occupancy = `at least ${unitSummary.occupancyRange.min} ${
|
|
13
|
+
unitSummary.occupancyRange.min == 1 ? t("t.person") : t("t.people")
|
|
14
|
+
}`
|
|
15
|
+
} else if (unitSummary.occupancyRange.max > 1) {
|
|
16
|
+
occupancy = `${unitSummary.occupancyRange.min}-${unitSummary.occupancyRange.max} ${
|
|
17
|
+
unitSummary.occupancyRange.max == 1 ? t("t.person") : t("t.people")
|
|
18
|
+
}`
|
|
19
|
+
} else {
|
|
20
|
+
occupancy = `1 ${t("t.person")}`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
unitType: <strong>{t("listings.unitTypes." + unitSummary.unitType.name)}</strong>,
|
|
25
|
+
occupancy: occupancy,
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return occupancyData
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const getOccupancyDescription = (listing: Listing) => {
|
|
34
|
+
const unitsSummarized = listing.unitsSummarized
|
|
35
|
+
if (
|
|
36
|
+
unitsSummarized &&
|
|
37
|
+
unitsSummarized.unitTypes &&
|
|
38
|
+
unitsSummarized.unitTypes.map((unitType) => unitType.name).includes("SRO")
|
|
39
|
+
) {
|
|
40
|
+
return unitsSummarized.unitTypes.length == 1
|
|
41
|
+
? t("listings.occupancyDescriptionAllSro")
|
|
42
|
+
: t("listings.occupancyDescriptionSomeSro")
|
|
43
|
+
} else {
|
|
44
|
+
return t("listings.occupancyDescriptionNoSro")
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ListingEvent, ListingEventType } from "@bloom-housing/backend-core/types"
|
|
2
|
+
|
|
3
|
+
export const cloudinaryPdfFromId = (publicId: string, cloudName: string) => {
|
|
4
|
+
return `https://res.cloudinary.com/${cloudName}/image/upload/${publicId}.pdf`
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const pdfUrlFromListingEvents = (
|
|
8
|
+
events: ListingEvent[],
|
|
9
|
+
listingType: ListingEventType,
|
|
10
|
+
cloudName: string
|
|
11
|
+
) => {
|
|
12
|
+
const event = events.find((event) => event.type === listingType)
|
|
13
|
+
if (event) {
|
|
14
|
+
return event.file?.label == "cloudinaryPDF"
|
|
15
|
+
? cloudinaryPdfFromId(event.file.fileId, cloudName)
|
|
16
|
+
: event.url
|
|
17
|
+
}
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Asset, Listing } from "@bloom-housing/backend-core/types"
|
|
2
|
+
|
|
3
|
+
export const cloudinaryUrlFromId = (publicId: string, size = 400) => {
|
|
4
|
+
const cloudName = process.env.cloudinaryCloudName || process.env.CLOUDINARY_CLOUD_NAME
|
|
5
|
+
return `https://res.cloudinary.com/${cloudName}/image/upload/w_${size},c_limit,q_65/${publicId}.jpg`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const imageUrlFromListing = (listing: Listing, size = 400) => {
|
|
9
|
+
// Use the new `image` field
|
|
10
|
+
const imageAssets = listing?.image ? [listing.image] : listing?.assets
|
|
11
|
+
|
|
12
|
+
// Fallback to `assets`
|
|
13
|
+
const cloudinaryBuilding = imageAssets?.find(
|
|
14
|
+
(asset: Asset) => asset.label == "cloudinaryBuilding"
|
|
15
|
+
)?.fileId
|
|
16
|
+
if (cloudinaryBuilding) return cloudinaryUrlFromId(cloudinaryBuilding, size)
|
|
17
|
+
|
|
18
|
+
return imageAssets?.find((asset: Asset) => asset.label == "building")?.fileId
|
|
19
|
+
}
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import {
|
|
3
|
+
InputType,
|
|
4
|
+
ApplicationPreference,
|
|
5
|
+
FormMetadataOptions,
|
|
6
|
+
Preference,
|
|
7
|
+
} from "@bloom-housing/backend-core/types"
|
|
8
|
+
import { UseFormMethods } from "react-hook-form"
|
|
9
|
+
import {
|
|
10
|
+
t,
|
|
11
|
+
GridSection,
|
|
12
|
+
ViewItem,
|
|
13
|
+
GridCell,
|
|
14
|
+
Field,
|
|
15
|
+
Select,
|
|
16
|
+
SelectOption,
|
|
17
|
+
resolveObject,
|
|
18
|
+
} from "@bloom-housing/ui-components"
|
|
19
|
+
import { stateKeys } from "./formOptions"
|
|
20
|
+
|
|
21
|
+
type ExtraFieldProps = {
|
|
22
|
+
metaKey: string
|
|
23
|
+
optionKey: string
|
|
24
|
+
extraKey: string
|
|
25
|
+
type: InputType
|
|
26
|
+
register: UseFormMethods["register"]
|
|
27
|
+
errors?: UseFormMethods["errors"]
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
hhMembersOptions?: SelectOption[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type FormAddressProps = {
|
|
33
|
+
subtitle: string
|
|
34
|
+
dataKey: string
|
|
35
|
+
type: AddressType
|
|
36
|
+
register: UseFormMethods["register"]
|
|
37
|
+
errors?: UseFormMethods["errors"]
|
|
38
|
+
required?: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type AddressType =
|
|
42
|
+
| "residence"
|
|
43
|
+
| "residence-member"
|
|
44
|
+
| "work"
|
|
45
|
+
| "mailing"
|
|
46
|
+
| "alternate"
|
|
47
|
+
| "preference"
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
Path to the preferences from listing object
|
|
51
|
+
*/
|
|
52
|
+
export const PREFERENCES_FORM_PATH = "application.preferences.options"
|
|
53
|
+
export const PREFERENCES_NONE_FORM_PATH = "application.preferences.none"
|
|
54
|
+
/*
|
|
55
|
+
It generates inner fields for preferences form
|
|
56
|
+
*/
|
|
57
|
+
export const ExtraField = ({
|
|
58
|
+
metaKey,
|
|
59
|
+
optionKey,
|
|
60
|
+
extraKey,
|
|
61
|
+
type,
|
|
62
|
+
register,
|
|
63
|
+
errors,
|
|
64
|
+
hhMembersOptions,
|
|
65
|
+
}: ExtraFieldProps) => {
|
|
66
|
+
const FIELD_NAME = `${PREFERENCES_FORM_PATH}.${metaKey}.${optionKey}.${extraKey}`
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className="my-4" key={FIELD_NAME}>
|
|
70
|
+
{(() => {
|
|
71
|
+
if (type === InputType.text) {
|
|
72
|
+
return (
|
|
73
|
+
<Field
|
|
74
|
+
id={FIELD_NAME}
|
|
75
|
+
name={FIELD_NAME}
|
|
76
|
+
type="text"
|
|
77
|
+
label={t(`application.preferences.options.${extraKey}`)}
|
|
78
|
+
register={register}
|
|
79
|
+
validation={{ required: true }}
|
|
80
|
+
error={!!resolveObject(FIELD_NAME, errors)}
|
|
81
|
+
errorMessage={t("errors.requiredFieldError")}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
} else if (type === InputType.address) {
|
|
85
|
+
return (
|
|
86
|
+
<div className="pb-4">
|
|
87
|
+
<FormAddress
|
|
88
|
+
subtitle={t("application.preferences.options.address")}
|
|
89
|
+
dataKey={FIELD_NAME}
|
|
90
|
+
type="preference"
|
|
91
|
+
register={register}
|
|
92
|
+
errors={errors}
|
|
93
|
+
required={true}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
)
|
|
97
|
+
} else if (type === InputType.hhMemberSelect) {
|
|
98
|
+
if (!hhMembersOptions)
|
|
99
|
+
return (
|
|
100
|
+
<Field
|
|
101
|
+
id={FIELD_NAME}
|
|
102
|
+
name={FIELD_NAME}
|
|
103
|
+
type="text"
|
|
104
|
+
label={t(`application.preferences.options.${extraKey}`)}
|
|
105
|
+
register={register}
|
|
106
|
+
validation={{ required: true }}
|
|
107
|
+
error={!!resolveObject(FIELD_NAME, errors)}
|
|
108
|
+
errorMessage={t("errors.requiredFieldError")}
|
|
109
|
+
/>
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<>
|
|
114
|
+
<Select
|
|
115
|
+
id={FIELD_NAME}
|
|
116
|
+
name={FIELD_NAME}
|
|
117
|
+
label={t(`application.preferences.options.${extraKey}`)}
|
|
118
|
+
register={register}
|
|
119
|
+
controlClassName="control"
|
|
120
|
+
placeholder={t("t.selectOne")}
|
|
121
|
+
options={hhMembersOptions}
|
|
122
|
+
validation={{ required: true }}
|
|
123
|
+
error={!!resolveObject(FIELD_NAME, errors)}
|
|
124
|
+
errorMessage={t("errors.requiredFieldError")}
|
|
125
|
+
/>
|
|
126
|
+
</>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return <></>
|
|
131
|
+
})()}
|
|
132
|
+
</div>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const FormAddress = ({
|
|
137
|
+
subtitle,
|
|
138
|
+
dataKey,
|
|
139
|
+
type,
|
|
140
|
+
register,
|
|
141
|
+
errors,
|
|
142
|
+
required,
|
|
143
|
+
}: FormAddressProps) => {
|
|
144
|
+
return (
|
|
145
|
+
<>
|
|
146
|
+
<GridSection subtitle={subtitle}>
|
|
147
|
+
<GridCell span={2}>
|
|
148
|
+
<ViewItem label={t("application.contact.streetAddress")}>
|
|
149
|
+
<Field
|
|
150
|
+
id={`${dataKey}.street`}
|
|
151
|
+
name={`${dataKey}.street`}
|
|
152
|
+
label={t("application.contact.streetAddress")}
|
|
153
|
+
placeholder={t("application.contact.streetAddress")}
|
|
154
|
+
register={register}
|
|
155
|
+
validation={{ required }}
|
|
156
|
+
error={!!resolveObject(`${dataKey}.street`, errors)}
|
|
157
|
+
errorMessage={t("errors.streetError")}
|
|
158
|
+
readerOnly
|
|
159
|
+
/>
|
|
160
|
+
</ViewItem>
|
|
161
|
+
</GridCell>
|
|
162
|
+
<GridCell>
|
|
163
|
+
<ViewItem label={t("application.contact.apt")}>
|
|
164
|
+
<Field
|
|
165
|
+
id={`${dataKey}.street2`}
|
|
166
|
+
name={`${dataKey}.street2`}
|
|
167
|
+
label={t("application.contact.apt")}
|
|
168
|
+
placeholder={t("application.contact.apt")}
|
|
169
|
+
register={register}
|
|
170
|
+
readerOnly
|
|
171
|
+
/>
|
|
172
|
+
</ViewItem>
|
|
173
|
+
</GridCell>
|
|
174
|
+
|
|
175
|
+
<GridCell>
|
|
176
|
+
<ViewItem label={t("application.contact.city")}>
|
|
177
|
+
<Field
|
|
178
|
+
id={`${dataKey}.city`}
|
|
179
|
+
name={`${dataKey}.city`}
|
|
180
|
+
label={t("application.contact.cityName")}
|
|
181
|
+
placeholder={t("application.contact.cityName")}
|
|
182
|
+
register={register}
|
|
183
|
+
validation={{ required }}
|
|
184
|
+
error={!!resolveObject(`${dataKey}.city`, errors)}
|
|
185
|
+
errorMessage={t("errors.cityError")}
|
|
186
|
+
readerOnly
|
|
187
|
+
/>
|
|
188
|
+
</ViewItem>
|
|
189
|
+
</GridCell>
|
|
190
|
+
|
|
191
|
+
<GridCell className="md:grid md:grid-cols-2 md:gap-8" span={2}>
|
|
192
|
+
<ViewItem label={t("application.contact.state")} className="mb-0">
|
|
193
|
+
<Select
|
|
194
|
+
id={`${dataKey}.state`}
|
|
195
|
+
name={`${dataKey}.state`}
|
|
196
|
+
label={t("application.contact.state")}
|
|
197
|
+
labelClassName="sr-only"
|
|
198
|
+
register={register}
|
|
199
|
+
controlClassName="control"
|
|
200
|
+
options={stateKeys}
|
|
201
|
+
keyPrefix="states"
|
|
202
|
+
validation={{ required }}
|
|
203
|
+
error={!!resolveObject(`${dataKey}.state`, errors)}
|
|
204
|
+
errorMessage={t("errors.stateError")}
|
|
205
|
+
/>
|
|
206
|
+
</ViewItem>
|
|
207
|
+
|
|
208
|
+
<ViewItem label={t("application.contact.zip")}>
|
|
209
|
+
<Field
|
|
210
|
+
id={`${dataKey}.zipCode`}
|
|
211
|
+
name={`${dataKey}.zipCode`}
|
|
212
|
+
label={t("application.contact.zip")}
|
|
213
|
+
placeholder={t("application.contact.zipCode")}
|
|
214
|
+
register={register}
|
|
215
|
+
validation={{ required }}
|
|
216
|
+
error={!!resolveObject(`${dataKey}.zipCode`, errors)}
|
|
217
|
+
errorMessage={t("errors.zipCodeError")}
|
|
218
|
+
readerOnly
|
|
219
|
+
/>
|
|
220
|
+
</ViewItem>
|
|
221
|
+
</GridCell>
|
|
222
|
+
|
|
223
|
+
{type === "residence" && (
|
|
224
|
+
<GridCell span={2}>
|
|
225
|
+
<Field
|
|
226
|
+
id="application.sendMailToMailingAddress"
|
|
227
|
+
name="application.sendMailToMailingAddress"
|
|
228
|
+
type="checkbox"
|
|
229
|
+
label={t("application.contact.sendMailToMailingAddress")}
|
|
230
|
+
register={register}
|
|
231
|
+
/>
|
|
232
|
+
</GridCell>
|
|
233
|
+
)}
|
|
234
|
+
</GridSection>
|
|
235
|
+
</>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
240
|
+
export const mapPreferencesToApi = (data: Record<string, any>) => {
|
|
241
|
+
if (!data.application?.preferences) return []
|
|
242
|
+
|
|
243
|
+
const CLAIMED_KEY = "claimed"
|
|
244
|
+
|
|
245
|
+
const preferencesFormData = data.application.preferences.options
|
|
246
|
+
|
|
247
|
+
const keys = Object.keys(preferencesFormData)
|
|
248
|
+
|
|
249
|
+
return keys.map((key) => {
|
|
250
|
+
const currentPreference = preferencesFormData[key]
|
|
251
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
252
|
+
const currentPreferenceValues = Object.values(currentPreference) as Record<string, any>
|
|
253
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
254
|
+
const claimed = currentPreferenceValues.map((c: { claimed: any }) => c.claimed).includes(true)
|
|
255
|
+
|
|
256
|
+
const options = Object.keys(currentPreference).map((option) => {
|
|
257
|
+
const currentOption = currentPreference[option]
|
|
258
|
+
|
|
259
|
+
// count keys except claimed
|
|
260
|
+
const extraKeys = Object.keys(currentOption).filter((item) => item !== CLAIMED_KEY)
|
|
261
|
+
|
|
262
|
+
const response = {
|
|
263
|
+
key: option,
|
|
264
|
+
checked: currentOption[CLAIMED_KEY],
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// assign extra data and detect data type
|
|
268
|
+
if (extraKeys.length) {
|
|
269
|
+
const extraData = extraKeys.map((extraKey) => {
|
|
270
|
+
const type = (() => {
|
|
271
|
+
if (typeof currentOption[extraKey] === "boolean") return InputType.boolean
|
|
272
|
+
// if object includes "city" property, it should be an address
|
|
273
|
+
if (Object.keys(currentOption[extraKey]).includes("city")) return InputType.address
|
|
274
|
+
|
|
275
|
+
return InputType.text
|
|
276
|
+
})()
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
key: extraKey,
|
|
280
|
+
type,
|
|
281
|
+
value: currentOption[extraKey],
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
Object.assign(response, { extraData })
|
|
286
|
+
} else {
|
|
287
|
+
Object.assign(response, { extraData: [] })
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return response
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
key,
|
|
295
|
+
claimed,
|
|
296
|
+
options,
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
302
|
+
export const mapApiToPreferencesForm = (preferences: ApplicationPreference[]) => {
|
|
303
|
+
const preferencesFormData = {}
|
|
304
|
+
|
|
305
|
+
preferences.forEach((item) => {
|
|
306
|
+
const options = item.options.reduce((acc, curr) => {
|
|
307
|
+
// extraData which comes from the API is an array, in the form we expect an object
|
|
308
|
+
const extraData =
|
|
309
|
+
curr?.extraData?.reduce((extraAcc, extraCurr) => {
|
|
310
|
+
// value - it can be string or nested address object
|
|
311
|
+
const value = extraCurr.value
|
|
312
|
+
Object.assign(extraAcc, {
|
|
313
|
+
[extraCurr.key]: value,
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
return extraAcc
|
|
317
|
+
}, {}) || {}
|
|
318
|
+
|
|
319
|
+
// each form option has "claimed" property - it's "checked" property in the API
|
|
320
|
+
const claimed = curr.checked
|
|
321
|
+
|
|
322
|
+
Object.assign(acc, {
|
|
323
|
+
[curr.key]: {
|
|
324
|
+
claimed,
|
|
325
|
+
...extraData,
|
|
326
|
+
},
|
|
327
|
+
})
|
|
328
|
+
return acc
|
|
329
|
+
}, {})
|
|
330
|
+
|
|
331
|
+
Object.assign(preferencesFormData, {
|
|
332
|
+
[item.key]: options,
|
|
333
|
+
})
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
const noneValues = preferences.reduce((acc, item) => {
|
|
337
|
+
const isClaimed = item.claimed
|
|
338
|
+
|
|
339
|
+
Object.assign(acc, {
|
|
340
|
+
[`${item.key}-none`]: !isClaimed,
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
return acc
|
|
344
|
+
}, {})
|
|
345
|
+
|
|
346
|
+
return { options: preferencesFormData, none: noneValues }
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/*
|
|
350
|
+
It generates checkbox name in proper prefrences structure
|
|
351
|
+
*/
|
|
352
|
+
export const getPreferenceOptionName = (key: string, metaKey: string, noneOption?: boolean) => {
|
|
353
|
+
if (noneOption) return getExclusivePreferenceOptionName(key)
|
|
354
|
+
else return getNormalPreferenceOptionName(metaKey, key)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export const getNormalPreferenceOptionName = (metaKey: string, key: string) => {
|
|
358
|
+
return `${PREFERENCES_FORM_PATH}.${metaKey}.${key}.claimed`
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export const getExclusivePreferenceOptionName = (key: string | undefined) => {
|
|
362
|
+
return `${PREFERENCES_NONE_FORM_PATH}.${key}-none`
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export type ExclusiveKey = {
|
|
366
|
+
optionKey: string
|
|
367
|
+
preferenceKey: string | undefined
|
|
368
|
+
}
|
|
369
|
+
/*
|
|
370
|
+
Create an array of all exclusive keys from a preference set
|
|
371
|
+
*/
|
|
372
|
+
export const getExclusiveKeys = (preferences: Preference[]) => {
|
|
373
|
+
const exclusive: ExclusiveKey[] = []
|
|
374
|
+
preferences?.forEach((preference) => {
|
|
375
|
+
preference?.formMetadata?.options.forEach((option: FormMetadataOptions) => {
|
|
376
|
+
if (option.exclusive)
|
|
377
|
+
exclusive.push({
|
|
378
|
+
optionKey: getPreferenceOptionName(option.key, preference?.formMetadata?.key ?? ""),
|
|
379
|
+
preferenceKey: preference?.formMetadata?.key,
|
|
380
|
+
})
|
|
381
|
+
})
|
|
382
|
+
if (!preference?.formMetadata?.hideGenericDecline)
|
|
383
|
+
exclusive.push({
|
|
384
|
+
optionKey: getExclusivePreferenceOptionName(preference?.formMetadata?.key),
|
|
385
|
+
preferenceKey: preference?.formMetadata?.key,
|
|
386
|
+
})
|
|
387
|
+
})
|
|
388
|
+
return exclusive
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const uncheckPreference = (
|
|
392
|
+
metaKey: string,
|
|
393
|
+
options: FormMetadataOptions[] | undefined,
|
|
394
|
+
setValue: (key: string, value: boolean) => void
|
|
395
|
+
) => {
|
|
396
|
+
options?.forEach((option) => {
|
|
397
|
+
setValue(getPreferenceOptionName(option.key, metaKey), false)
|
|
398
|
+
})
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/*
|
|
402
|
+
Set the value of an exclusive checkbox, unchecking all the appropriate boxes in response to the value
|
|
403
|
+
*/
|
|
404
|
+
export const setExclusive = (
|
|
405
|
+
value: boolean,
|
|
406
|
+
setValue: (key: string, value: boolean) => void,
|
|
407
|
+
exclusiveKeys: ExclusiveKey[],
|
|
408
|
+
key: string,
|
|
409
|
+
preference: Preference
|
|
410
|
+
) => {
|
|
411
|
+
if (value) {
|
|
412
|
+
// Uncheck all other keys if setting an exclusive key to true
|
|
413
|
+
uncheckPreference(
|
|
414
|
+
preference?.formMetadata?.key ?? "",
|
|
415
|
+
preference?.formMetadata?.options,
|
|
416
|
+
setValue
|
|
417
|
+
)
|
|
418
|
+
setValue(key ?? "", true)
|
|
419
|
+
} else {
|
|
420
|
+
// Uncheck all exclusive keys if setting a normal key to true
|
|
421
|
+
exclusiveKeys.forEach((thisKey) => {
|
|
422
|
+
if (thisKey.preferenceKey === preference?.formMetadata?.key)
|
|
423
|
+
setValue(thisKey.optionKey, false)
|
|
424
|
+
})
|
|
425
|
+
}
|
|
426
|
+
}
|