@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.
Files changed (223) hide show
  1. package/.jest/setup-tests.js +24 -0
  2. package/CHANGELOG.md +20 -0
  3. package/README.md +195 -0
  4. package/index.ts +148 -0
  5. package/jest.config.js +41 -0
  6. package/package.json +98 -0
  7. package/public/images/alameda-logo-white.svg +1 -0
  8. package/public/images/arrow-down.png +0 -0
  9. package/public/images/arrow-down.svg +1 -0
  10. package/public/images/check.png +0 -0
  11. package/public/images/check.svg +11 -0
  12. package/public/images/eho-logo-white.svg +1 -0
  13. package/public/images/eho-logo.svg +1 -0
  14. package/public/images/logo_glyph.svg +11 -0
  15. package/src/actions/Button.scss +157 -0
  16. package/src/actions/Button.tsx +80 -0
  17. package/src/actions/ExpandableContent.tsx +29 -0
  18. package/src/actions/ExpandableText.scss +18 -0
  19. package/src/actions/ExpandableText.tsx +52 -0
  20. package/src/actions/LinkButton.tsx +30 -0
  21. package/src/actions/LocalizedLink.tsx +11 -0
  22. package/src/authentication/AuthContext.ts +327 -0
  23. package/src/authentication/RequireLogin.tsx +62 -0
  24. package/src/authentication/index.ts +5 -0
  25. package/src/authentication/timeout.tsx +127 -0
  26. package/src/authentication/token.ts +17 -0
  27. package/src/authentication/useRequireLoggedInUser.ts +19 -0
  28. package/src/blocks/ActionBlock.scss +108 -0
  29. package/src/blocks/ActionBlock.tsx +51 -0
  30. package/src/blocks/AppStatusItem.scss +140 -0
  31. package/src/blocks/AppStatusItem.tsx +75 -0
  32. package/src/blocks/DashBlock.tsx +42 -0
  33. package/src/blocks/DashBlocks.scss +56 -0
  34. package/src/blocks/DashBlocks.tsx +7 -0
  35. package/src/blocks/FormCard.scss +201 -0
  36. package/src/blocks/FormCard.tsx +29 -0
  37. package/src/blocks/HousingCounselor.tsx +51 -0
  38. package/src/blocks/ImageCard.scss +91 -0
  39. package/src/blocks/ImageCard.tsx +77 -0
  40. package/src/blocks/InfoCard.scss +42 -0
  41. package/src/blocks/InfoCard.tsx +44 -0
  42. package/src/blocks/StatusBar.scss +30 -0
  43. package/src/blocks/StatusBar.tsx +31 -0
  44. package/src/blocks/ViewItem.scss +59 -0
  45. package/src/blocks/ViewItem.tsx +32 -0
  46. package/src/config/ConfigContext.tsx +36 -0
  47. package/src/config/NavigationContext.tsx +54 -0
  48. package/src/config/index.ts +2 -0
  49. package/src/footers/ExygyFooter.tsx +12 -0
  50. package/src/footers/SiteFooter.scss +28 -0
  51. package/src/footers/SiteFooter.tsx +10 -0
  52. package/src/forms/CloudinaryUpload.ts +50 -0
  53. package/src/forms/DOBField.tsx +132 -0
  54. package/src/forms/DateField.tsx +120 -0
  55. package/src/forms/Dropzone.scss +17 -0
  56. package/src/forms/Dropzone.tsx +67 -0
  57. package/src/forms/Field.tsx +115 -0
  58. package/src/forms/FieldGroup.tsx +82 -0
  59. package/src/forms/Form.tsx +22 -0
  60. package/src/forms/HouseholdMemberForm.tsx +41 -0
  61. package/src/forms/HouseholdSizeField.tsx +74 -0
  62. package/src/forms/PhoneField.tsx +69 -0
  63. package/src/forms/PhoneMask.tsx +24 -0
  64. package/src/forms/Select.tsx +80 -0
  65. package/src/forms/Textarea.scss +40 -0
  66. package/src/forms/Textarea.tsx +64 -0
  67. package/src/forms/TimeField.tsx +176 -0
  68. package/src/global/AppearanceTypes.ts +46 -0
  69. package/src/global/ApplicationStatusType.ts +6 -0
  70. package/src/global/accordion.scss +4 -0
  71. package/src/global/blocks.scss +137 -0
  72. package/src/global/custom_counter.scss +50 -0
  73. package/src/global/forms.scss +362 -0
  74. package/src/global/headers.scss +89 -0
  75. package/src/global/homepage.scss +8 -0
  76. package/src/global/index.scss +72 -0
  77. package/src/global/lists.scss +21 -0
  78. package/src/global/markdown.scss +33 -0
  79. package/src/global/mixins.scss +175 -0
  80. package/src/global/navbar.scss +280 -0
  81. package/src/global/print.scss +59 -0
  82. package/src/global/tables.scss +197 -0
  83. package/src/global/text.scss +141 -0
  84. package/src/global/vendor/AgPagination.tsx +133 -0
  85. package/src/global/vendor/_setup_bulma.scss +31 -0
  86. package/src/global/vendor/ag_grid.scss +140 -0
  87. package/src/headers/Hero.scss +56 -0
  88. package/src/headers/Hero.tsx +76 -0
  89. package/src/headers/PageHeader.scss +31 -0
  90. package/src/headers/PageHeader.tsx +39 -0
  91. package/src/headers/SiteHeader.tsx +136 -0
  92. package/src/helpers/address.tsx +46 -0
  93. package/src/helpers/blankApplication.ts +108 -0
  94. package/src/helpers/capitalize.tsx +7 -0
  95. package/src/helpers/dateToString.ts +11 -0
  96. package/src/helpers/debounce.ts +12 -0
  97. package/src/helpers/formOptions.tsx +229 -0
  98. package/src/helpers/formatYesNoLabel.ts +9 -0
  99. package/src/helpers/getTranslationWithArguments.ts +14 -0
  100. package/src/helpers/links.ts +7 -0
  101. package/src/helpers/localeRoute.tsx +13 -0
  102. package/src/helpers/mergeDeep.ts +12 -0
  103. package/src/helpers/nextjs.ts +7 -0
  104. package/src/helpers/numberOrdinal.ts +17 -0
  105. package/src/helpers/occupancyFormatting.tsx +46 -0
  106. package/src/helpers/pdfs.ts +19 -0
  107. package/src/helpers/photos.ts +19 -0
  108. package/src/helpers/preferences.tsx +426 -0
  109. package/src/helpers/resolveObject.ts +5 -0
  110. package/src/helpers/state.tsx +7 -0
  111. package/src/helpers/tableSummaries.tsx +80 -0
  112. package/src/helpers/translator.tsx +37 -0
  113. package/src/helpers/useKeyPress.ts +17 -0
  114. package/src/helpers/useMutate.ts +40 -0
  115. package/src/helpers/useOutsideClick.ts +25 -0
  116. package/src/helpers/validators.ts +3 -0
  117. package/src/icons/HeaderBadge.scss +29 -0
  118. package/src/icons/HeaderBadge.tsx +38 -0
  119. package/src/icons/Icon.scss +76 -0
  120. package/src/icons/Icon.tsx +145 -0
  121. package/src/icons/Icons.tsx +556 -0
  122. package/src/lists/PreferencesList.scss +72 -0
  123. package/src/lists/PreferencesList.tsx +60 -0
  124. package/src/locales/es.json +745 -0
  125. package/src/locales/general.json +1307 -0
  126. package/src/locales/general_OLD.json +868 -0
  127. package/src/locales/vi.json +745 -0
  128. package/src/locales/zh.json +745 -0
  129. package/src/navigation/Breadcrumbs.scss +25 -0
  130. package/src/navigation/Breadcrumbs.tsx +27 -0
  131. package/src/navigation/FooterNav.scss +47 -0
  132. package/src/navigation/FooterNav.tsx +19 -0
  133. package/src/navigation/LanguageNav.scss +32 -0
  134. package/src/navigation/LanguageNav.tsx +53 -0
  135. package/src/navigation/ProgressNav.scss +102 -0
  136. package/src/navigation/ProgressNav.tsx +50 -0
  137. package/src/navigation/TabNav.scss +38 -0
  138. package/src/navigation/TabNav.tsx +69 -0
  139. package/src/navigation/Tabs.scss +65 -0
  140. package/src/navigation/Tabs.tsx +93 -0
  141. package/src/navigation/UserNav.tsx +37 -0
  142. package/src/notifications/AlertBox.scss +78 -0
  143. package/src/notifications/AlertBox.tsx +79 -0
  144. package/src/notifications/AlertNotice.scss +58 -0
  145. package/src/notifications/AlertNotice.tsx +37 -0
  146. package/src/notifications/ApplicationStatus.scss +10 -0
  147. package/src/notifications/ApplicationStatus.tsx +64 -0
  148. package/src/notifications/ErrorMessage.tsx +15 -0
  149. package/src/notifications/SiteAlert.tsx +54 -0
  150. package/src/notifications/StatusAside.scss +11 -0
  151. package/src/notifications/StatusAside.tsx +25 -0
  152. package/src/notifications/StatusMessage.scss +25 -0
  153. package/src/notifications/StatusMessage.tsx +59 -0
  154. package/src/notifications/alertTypes.ts +7 -0
  155. package/src/notifications/index.ts +4 -0
  156. package/src/overlays/Drawer.scss +105 -0
  157. package/src/overlays/Drawer.tsx +51 -0
  158. package/src/overlays/LoadingOverlay.scss +25 -0
  159. package/src/overlays/LoadingOverlay.tsx +29 -0
  160. package/src/overlays/Modal.scss +55 -0
  161. package/src/overlays/Modal.tsx +61 -0
  162. package/src/overlays/Overlay.scss +50 -0
  163. package/src/overlays/Overlay.tsx +100 -0
  164. package/src/page_components/listing/AdditionalFees.tsx +56 -0
  165. package/src/page_components/listing/ListingCard.scss +47 -0
  166. package/src/page_components/listing/ListingCard.tsx +34 -0
  167. package/src/page_components/listing/ListingDetailHeader.tsx +25 -0
  168. package/src/page_components/listing/ListingDetails.tsx +29 -0
  169. package/src/page_components/listing/ListingMap.scss +36 -0
  170. package/src/page_components/listing/ListingMap.tsx +138 -0
  171. package/src/page_components/listing/ListingsGroup.scss +65 -0
  172. package/src/page_components/listing/ListingsGroup.tsx +49 -0
  173. package/src/page_components/listing/UnitTables.tsx +111 -0
  174. package/src/page_components/listing/listing_sidebar/ApplicationSection.tsx +49 -0
  175. package/src/page_components/listing/listing_sidebar/Apply.tsx +225 -0
  176. package/src/page_components/listing/listing_sidebar/LeasingAgent.tsx +77 -0
  177. package/src/page_components/listing/listing_sidebar/ListingUpdated.tsx +20 -0
  178. package/src/page_components/listing/listing_sidebar/ReferralApplication.tsx +28 -0
  179. package/src/page_components/listing/listing_sidebar/SidebarAddress.tsx +56 -0
  180. package/src/page_components/listing/listing_sidebar/Waitlist.tsx +94 -0
  181. package/src/page_components/listing/listing_sidebar/WhatToExpect.tsx +22 -0
  182. package/src/page_components/listing/listing_sidebar/events/DownloadLotteryResults.tsx +34 -0
  183. package/src/page_components/listing/listing_sidebar/events/EventDateSection.tsx +24 -0
  184. package/src/page_components/listing/listing_sidebar/events/LotteryResultsEvent.tsx +26 -0
  185. package/src/page_components/listing/listing_sidebar/events/OpenHouseEvent.tsx +27 -0
  186. package/src/page_components/listing/listing_sidebar/events/PublicLotteryEvent.tsx +22 -0
  187. package/src/prototypes/AppCard.scss +64 -0
  188. package/src/prototypes/Back.scss +19 -0
  189. package/src/prototypes/ButtonGroup.scss +6 -0
  190. package/src/prototypes/ButtonPager.scss +22 -0
  191. package/src/prototypes/FieldSection.scss +35 -0
  192. package/src/prototypes/FieldSection.tsx +31 -0
  193. package/src/prototypes/GridItem.tsx +15 -0
  194. package/src/prototypes/SideNav.scss +32 -0
  195. package/src/prototypes/SideNav.tsx +14 -0
  196. package/src/prototypes/SummaryCard.scss +34 -0
  197. package/src/sections/ContentSection.scss +15 -0
  198. package/src/sections/ContentSection.tsx +25 -0
  199. package/src/sections/FooterSection.scss +6 -0
  200. package/src/sections/FooterSection.tsx +16 -0
  201. package/src/sections/GridSection.scss +72 -0
  202. package/src/sections/GridSection.tsx +82 -0
  203. package/src/sections/InfoCardGrid.scss +45 -0
  204. package/src/sections/InfoCardGrid.tsx +20 -0
  205. package/src/sections/ListSection.scss +7 -0
  206. package/src/sections/ListSection.tsx +23 -0
  207. package/src/sections/MarkdownSection.scss +13 -0
  208. package/src/sections/MarkdownSection.tsx +21 -0
  209. package/src/sections/ResponsiveContentList.tsx +67 -0
  210. package/src/sections/ResponsiveWrappers.tsx +23 -0
  211. package/src/tables/GroupedTable.tsx +86 -0
  212. package/src/tables/MinimalTable.tsx +32 -0
  213. package/src/tables/ResponsiveTable.tsx +24 -0
  214. package/src/tables/StandardTable.tsx +229 -0
  215. package/src/text/Description.scss +52 -0
  216. package/src/text/Description.tsx +24 -0
  217. package/src/text/Message.scss +16 -0
  218. package/src/text/Message.tsx +16 -0
  219. package/src/text/Tag.scss +94 -0
  220. package/src/text/Tag.tsx +22 -0
  221. package/tailwind.config.js +128 -0
  222. package/tailwind.tosass.js +29 -0
  223. package/tsconfig.json +31 -0
@@ -0,0 +1,49 @@
1
+ import * as React from "react"
2
+ import moment from "moment"
3
+ import { Listing } from "@bloom-housing/backend-core/types"
4
+ import { Apply } from "./Apply"
5
+ import { Waitlist } from "./Waitlist"
6
+
7
+ export interface ApplicationSectionProps {
8
+ listing: Listing
9
+ internalFormRoute: string
10
+ preview?: boolean
11
+ cloudName?: string
12
+ }
13
+
14
+ const showWaitlist = (listing: Listing) => {
15
+ // Hide waitlist for FCFS and when there are no waitlist spots
16
+ return (
17
+ listing.applicationDueDate != null &&
18
+ listing.isWaitlistOpen &&
19
+ listing.waitlistOpenSpots &&
20
+ listing.waitlistOpenSpots > 0
21
+ )
22
+ }
23
+
24
+ const ApplicationSection = (props: ApplicationSectionProps) => {
25
+ const { listing, preview } = props
26
+ const dueDate = moment(listing.applicationDueDate)
27
+ const nowTime = moment()
28
+
29
+ // If applications are closed, hide this section
30
+ if (nowTime > dueDate) return null
31
+
32
+ return (
33
+ <>
34
+ {showWaitlist(listing) && (
35
+ <section className="aside-block is-tinted">
36
+ <Waitlist listing={listing} />
37
+ </section>
38
+ )}
39
+ <Apply
40
+ listing={listing}
41
+ preview={preview}
42
+ internalFormRoute={props.internalFormRoute}
43
+ cloudName={props.cloudName}
44
+ />
45
+ </>
46
+ )
47
+ }
48
+
49
+ export { ApplicationSection as default, ApplicationSection }
@@ -0,0 +1,225 @@
1
+ import React, { useState } from "react"
2
+ import {
3
+ Listing,
4
+ ApplicationMethod,
5
+ ApplicationMethodType,
6
+ ListingApplicationAddressType,
7
+ PaperApplication,
8
+ } from "@bloom-housing/backend-core/types"
9
+ import moment from "moment"
10
+ import { t } from "../../../helpers/translator"
11
+ import { Button } from "../../../actions/Button"
12
+ import { LinkButton } from "../../../actions/LinkButton"
13
+ import { SidebarAddress } from "./SidebarAddress"
14
+ import { openDateState } from "../../../helpers/state"
15
+ import { AppearanceStyleType } from "../../../global/AppearanceTypes"
16
+ import { cloudinaryPdfFromId } from "../../../helpers/pdfs"
17
+
18
+ export interface ApplyProps {
19
+ listing: Listing
20
+ internalFormRoute: string
21
+ preview?: boolean
22
+ cloudName?: string
23
+ }
24
+
25
+ const hasMethod = (applicationMethods: ApplicationMethod[], type: ApplicationMethodType) => {
26
+ return applicationMethods.some((method) => method.type == type)
27
+ }
28
+
29
+ const getMethod = (applicationMethods: ApplicationMethod[], type: ApplicationMethodType) => {
30
+ return applicationMethods.find((method) => method.type == type)
31
+ }
32
+
33
+ const OrDivider = (props: { bgColor: string }) => (
34
+ <div className="aside-block__divider">
35
+ <span className={`bg-${props.bgColor} aside-block__conjunction`}>{t("t.or")}</span>
36
+ </div>
37
+ )
38
+
39
+ const SubHeader = (props: { text: string }) => <h3 className="text-caps-tiny">{props.text}</h3>
40
+
41
+ const NumberedHeader = (props: { num: number; text: string }) => (
42
+ <div className="text-serif-lg">
43
+ <span className="text-primary pr-1">{props.num}</span>
44
+ {props.text}
45
+ </div>
46
+ )
47
+
48
+ const Apply = (props: ApplyProps) => {
49
+ // /applications/start/choose-language
50
+ const { listing, internalFormRoute, preview } = props
51
+ let onlineApplicationUrl = ""
52
+
53
+ const [showDownload, setShowDownload] = useState(false)
54
+ const toggleDownload = () => setShowDownload(!showDownload)
55
+
56
+ const openDate = moment(listing.applicationOpenDate).format("MMMM D, YYYY")
57
+
58
+ if (hasMethod(listing.applicationMethods, ApplicationMethodType.Internal)) {
59
+ onlineApplicationUrl = `${internalFormRoute}?listingId=${listing.id}`
60
+ } else if (hasMethod(listing.applicationMethods, ApplicationMethodType.ExternalLink)) {
61
+ onlineApplicationUrl =
62
+ getMethod(listing.applicationMethods, ApplicationMethodType.ExternalLink)
63
+ ?.externalReference || ""
64
+ }
65
+
66
+ const paperMethod = getMethod(listing.applicationMethods, ApplicationMethodType.FileDownload)
67
+ const sortedPaperApplications = paperMethod?.paperApplications?.sort((a, b) => {
68
+ // Ensure English is always first
69
+ if (a.language == "en") {
70
+ return -1
71
+ }
72
+ if (b.language == "en") {
73
+ return 1
74
+ }
75
+
76
+ // Otherwise, do regular string sort
77
+ const aLang = t(`languages.${a.language}`)
78
+ const bLang = t(`languages.${b.language}`)
79
+ if (aLang < bLang) {
80
+ return -1
81
+ }
82
+ if (aLang > bLang) {
83
+ return 1
84
+ }
85
+ return 0
86
+ })
87
+
88
+ type AddressLocation = "dropOff" | "pickUp"
89
+
90
+ const getAddress = (
91
+ addressType: ListingApplicationAddressType | undefined,
92
+ location: AddressLocation
93
+ ) => {
94
+ if (addressType === ListingApplicationAddressType.leasingAgent) {
95
+ return listing.leasingAgentAddress
96
+ }
97
+ if (addressType === ListingApplicationAddressType.mailingAddress) {
98
+ return listing.applicationMailingAddress
99
+ }
100
+ if (location === "dropOff") {
101
+ return listing.applicationDropOffAddress
102
+ } else {
103
+ return listing.applicationPickUpAddress
104
+ }
105
+ }
106
+
107
+ return (
108
+ <>
109
+ <section className="aside-block">
110
+ <h2 className="text-caps-underline">{t("listings.apply.howToApply")}</h2>
111
+ {openDateState(listing) && (
112
+ <p className="mb-5 text-gray-700">
113
+ {t("listings.apply.applicationWillBeAvailableOn", { openDate: openDate })}
114
+ </p>
115
+ )}
116
+ {!openDateState(listing) && onlineApplicationUrl !== "" && (
117
+ <>
118
+ {preview ? (
119
+ <Button disabled className="w-full mb-2">
120
+ {t("listings.apply.applyOnline")}
121
+ </Button>
122
+ ) : (
123
+ <LinkButton
124
+ styleType={AppearanceStyleType.primary}
125
+ className="w-full mb-2"
126
+ href={onlineApplicationUrl}
127
+ >
128
+ {t("listings.apply.applyOnline")}
129
+ </LinkButton>
130
+ )}
131
+ </>
132
+ )}
133
+ {!openDateState(listing) && paperMethod && (
134
+ <>
135
+ {onlineApplicationUrl !== "" && <OrDivider bgColor="white" />}
136
+ <NumberedHeader num={1} text={t("listings.apply.getAPaperApplication")} />
137
+ <Button
138
+ styleType={
139
+ !preview && onlineApplicationUrl === "" ? AppearanceStyleType.primary : undefined
140
+ }
141
+ className="w-full mb-2"
142
+ onClick={toggleDownload}
143
+ disabled={preview}
144
+ >
145
+ {t("listings.apply.downloadApplication")}
146
+ </Button>
147
+ </>
148
+ )}
149
+ {showDownload &&
150
+ sortedPaperApplications?.map((paperApplication: PaperApplication) => (
151
+ <p key={paperApplication?.file?.fileId} className="text-center mt-2 mb-4 text-sm">
152
+ <a
153
+ href={
154
+ paperApplication?.file?.fileId.includes("https")
155
+ ? paperApplication?.file?.fileId
156
+ : cloudinaryPdfFromId(
157
+ paperApplication?.file?.fileId || "",
158
+ props.cloudName || ""
159
+ )
160
+ }
161
+ title={t("listings.apply.downloadApplication")}
162
+ target="_blank"
163
+ >
164
+ {t(`languages.${paperApplication.language}`)}
165
+ </a>
166
+ </p>
167
+ ))}
168
+ {(listing.applicationPickUpAddress || listing.applicationPickUpAddressType) && (
169
+ <>
170
+ {!openDateState(listing) && (onlineApplicationUrl !== "" || paperMethod) && (
171
+ <OrDivider bgColor="white" />
172
+ )}
173
+ <SubHeader text={t("listings.apply.pickUpAnApplication")} />
174
+ <SidebarAddress
175
+ address={getAddress(listing.applicationPickUpAddressType, "pickUp")}
176
+ officeHours={listing.applicationPickUpAddressOfficeHours}
177
+ />
178
+ </>
179
+ )}
180
+ </section>
181
+
182
+ {(listing.applicationMailingAddress ||
183
+ listing.applicationDropOffAddress ||
184
+ listing.applicationDropOffAddressType) && (
185
+ <section className="aside-block is-tinted bg-gray-100">
186
+ <NumberedHeader num={2} text={t("listings.apply.submitAPaperApplication")} />
187
+ {listing.applicationMailingAddress && (
188
+ <>
189
+ <SubHeader text={t("listings.apply.sendByUsMail")} />
190
+ <p className="text-gray-700">{listing.applicationOrganization}</p>
191
+ <SidebarAddress address={listing.applicationMailingAddress} />
192
+ <p className="mt-4 text-tiny text-gray-750">
193
+ {listing.postmarkedApplicationsReceivedByDate
194
+ ? t("listings.apply.postmarkedApplicationsMustBeReceivedByDate", {
195
+ applicationDueDate: moment(listing.applicationDueDate).format(
196
+ `MMM. DD, YYYY [${t("t.at")}] h A`
197
+ ),
198
+ postmarkReceivedByDate: moment(
199
+ listing.postmarkedApplicationsReceivedByDate
200
+ ).format(`MMM. DD, YYYY [${t("t.at")}] h A`),
201
+ developer: listing.developer,
202
+ })
203
+ : t("listings.apply.applicationsMustBeReceivedByDeadline")}
204
+ </p>
205
+ </>
206
+ )}
207
+ {listing.applicationMailingAddress && listing.applicationDropOffAddress && (
208
+ <OrDivider bgColor="gray-100" />
209
+ )}
210
+ {(listing.applicationDropOffAddress || listing.applicationDropOffAddressType) && (
211
+ <>
212
+ <SubHeader text={t("listings.apply.dropOffApplication")} />
213
+ <SidebarAddress
214
+ address={getAddress(listing.applicationDropOffAddressType, "dropOff")}
215
+ officeHours={listing.applicationDropOffAddressOfficeHours}
216
+ />
217
+ </>
218
+ )}
219
+ </section>
220
+ )}
221
+ </>
222
+ )
223
+ }
224
+
225
+ export { Apply as default, Apply }
@@ -0,0 +1,77 @@
1
+ import * as React from "react"
2
+ import { SidebarAddress } from "./SidebarAddress"
3
+ import { t } from "../../../helpers/translator"
4
+ import { Icon, IconFillColors } from "../../../icons/Icon"
5
+ import { Listing } from "@bloom-housing/backend-core/types"
6
+ import { openDateState } from "../../../helpers/state"
7
+
8
+ interface LeasingAgentProps {
9
+ listing: Listing
10
+ managementCompany?: { name: string; website: string }
11
+ }
12
+
13
+ const LeasingAgent = (props: LeasingAgentProps) => {
14
+ const listing = props.listing
15
+
16
+ if (openDateState(listing)) {
17
+ return null
18
+ }
19
+
20
+ const phoneNumber = listing.leasingAgentPhone
21
+ ? `tel:${listing.leasingAgentPhone.replace(/[-()]/g, "")}`
22
+ : ""
23
+
24
+ let managementWebsite = props.managementCompany?.website
25
+ if (managementWebsite && !managementWebsite.startsWith("http")) {
26
+ managementWebsite = `http://${managementWebsite}`
27
+ }
28
+
29
+ return (
30
+ <section className="aside-block">
31
+ <h4 className="text-caps-underline">{t("leasingAgent.contact")}</h4>
32
+
33
+ {listing.leasingAgentName && <p className="text-xl">{listing.leasingAgentName}</p>}
34
+ {listing.leasingAgentTitle && <p className="text-gray-700">{listing.leasingAgentTitle}</p>}
35
+ {props.managementCompany?.name && (
36
+ <p className="text-gray-700">{props.managementCompany.name}</p>
37
+ )}
38
+
39
+ {listing.leasingAgentPhone && (
40
+ <>
41
+ <p className="mt-5">
42
+ <a href={phoneNumber}>
43
+ <Icon symbol="phone" size="medium" fill={IconFillColors.primary} /> {t("t.call")}{" "}
44
+ {listing.leasingAgentPhone}
45
+ </a>
46
+ </p>
47
+ <p className="text-sm text-gray-700">{t("leasingAgent.dueToHighCallVolume")}</p>
48
+ </>
49
+ )}
50
+
51
+ {listing.leasingAgentEmail && (
52
+ <p className="my-5">
53
+ <a href={`mailto:${listing.leasingAgentEmail}`}>
54
+ <Icon symbol="mail" size="medium" fill={IconFillColors.primary} /> {t("t.email")}
55
+ </a>
56
+ </p>
57
+ )}
58
+
59
+ {managementWebsite && (
60
+ <p className="my-5">
61
+ <a href={managementWebsite} target="_blank" rel="noreferrer noopener">
62
+ <Icon symbol="globe" size="medium" fill={IconFillColors.primary} /> {t("t.website")}
63
+ </a>
64
+ </p>
65
+ )}
66
+
67
+ {listing.leasingAgentAddress && (
68
+ <SidebarAddress
69
+ address={listing.leasingAgentAddress}
70
+ officeHours={listing.leasingAgentOfficeHours}
71
+ />
72
+ )}
73
+ </section>
74
+ )
75
+ }
76
+
77
+ export { LeasingAgent as default, LeasingAgent }
@@ -0,0 +1,20 @@
1
+ import * as React from "react"
2
+ import { t } from "../../../helpers/translator"
3
+ import moment from "moment"
4
+
5
+ interface ListingUpdatedProps {
6
+ listingUpdated: Date
7
+ }
8
+
9
+ const ListingUpdated = (props: ListingUpdatedProps) => {
10
+ const listingUpdated = props.listingUpdated
11
+ return (
12
+ <section className="aside-block">
13
+ <p className="text-tiny text-gray-800">
14
+ {`${t("listings.listingUpdated")}: ${moment(listingUpdated).format("MMMM DD, YYYY")}`}
15
+ </p>
16
+ </section>
17
+ )
18
+ }
19
+
20
+ export { ListingUpdated as default, ListingUpdated }
@@ -0,0 +1,28 @@
1
+ import * as React from "react"
2
+ import { t } from "../../../helpers/translator"
3
+ import { Icon, IconFillColors } from "../../../icons/Icon"
4
+
5
+ interface ReferralApplicationProps {
6
+ description: string
7
+ phoneNumber: string
8
+ title: string
9
+ }
10
+
11
+ const ReferralApplication = (props: ReferralApplicationProps) => {
12
+ const linkedPhoneNumber = `tel:${props.phoneNumber.replace(/[-()]/g, "")}`
13
+
14
+ return (
15
+ <section className="aside-block">
16
+ <h2 className="text-caps-underline">{props.title}</h2>
17
+ <p>
18
+ <a href={linkedPhoneNumber}>
19
+ <Icon symbol="phone" size="medium" fill={IconFillColors.primary} /> {t("t.call")}{" "}
20
+ {props.phoneNumber}
21
+ </a>
22
+ </p>
23
+ <p className="text-tiny mt-4 text-gray-800">{props.description}</p>
24
+ </section>
25
+ )
26
+ }
27
+
28
+ export { ReferralApplication as default, ReferralApplication }
@@ -0,0 +1,56 @@
1
+ import * as React from "react"
2
+ import ReactDOMServer from "react-dom/server"
3
+ import { Icon, IconFillColors } from "../../../icons/Icon"
4
+ import { OneLineAddress, MultiLineAddress, Address } from "../../../helpers/address"
5
+ import { t } from "../../../helpers/translator"
6
+ import Markdown from "markdown-to-jsx"
7
+
8
+ export interface SidebarAddressProps {
9
+ address?: Address
10
+ officeHours?: string
11
+ }
12
+
13
+ const SidebarAddress = (props: SidebarAddressProps) => {
14
+ const { address, officeHours } = props
15
+ let mainAddress = null
16
+ let googleMapsHref = ""
17
+ let hours = <></>
18
+
19
+ if (address?.street) {
20
+ const oneLineAddress = <OneLineAddress address={address} />
21
+ mainAddress = <MultiLineAddress address={address} />
22
+
23
+ googleMapsHref =
24
+ "https://www.google.com/maps/place/" + ReactDOMServer.renderToStaticMarkup(oneLineAddress)
25
+ }
26
+
27
+ if (officeHours) {
28
+ hours = (
29
+ <>
30
+ <h3 className="text-caps-tiny ">{t("leasingAgent.officeHours")}</h3>
31
+ <div className="text-gray-800 text-tiny markdown">
32
+ <Markdown children={officeHours} options={{ disableParsingRawHTML: true }} />
33
+ </div>
34
+ </>
35
+ )
36
+ }
37
+
38
+ return (
39
+ <>
40
+ {address?.street && (
41
+ <>
42
+ <p className="text-gray-700 mb-1">{mainAddress}</p>
43
+ <p className="mb-4">
44
+ <a href={googleMapsHref} className="inline-block pt-1" target="_blank">
45
+ <Icon symbol="map" size="medium" fill={IconFillColors.primary} />{" "}
46
+ {t("t.getDirections")}
47
+ </a>
48
+ </p>
49
+ </>
50
+ )}
51
+ {hours}
52
+ </>
53
+ )
54
+ }
55
+
56
+ export { SidebarAddress as default, SidebarAddress }