@bloom-housing/ui-components 4.2.1-alpha.0 → 4.2.1-alpha.1
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/CHANGELOG.md +8 -0
- package/index.ts +1 -0
- package/package.json +3 -3
- package/src/authentication/AuthContext.ts +23 -13
- package/src/authentication/RequireLogin.tsx +28 -3
- package/src/locales/general.json +5 -1
- package/src/overlays/Overlay.scss +2 -2
- package/src/page_components/sign-in/FormTerms.tsx +94 -0
- package/src/sections/MarkdownSection.scss +4 -1
- package/src/sections/MarkdownSection.tsx +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [4.2.1-alpha.1](https://github.com/bloom-housing/bloom/compare/@bloom-housing/ui-components@4.2.1-alpha.0...@bloom-housing/ui-components@4.2.1-alpha.1) (2022-04-07)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @bloom-housing/ui-components
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
## [4.2.1-alpha.0](https://github.com/bloom-housing/bloom/compare/@bloom-housing/ui-components@4.1.3-alpha.5...@bloom-housing/ui-components@4.2.1-alpha.0) (2022-04-06)
|
|
7
15
|
|
|
8
16
|
|
package/index.ts
CHANGED
|
@@ -118,6 +118,7 @@ export * from "./src/page_components/listing/listing_sidebar/Waitlist"
|
|
|
118
118
|
export * from "./src/page_components/listing/listing_sidebar/WhatToExpect"
|
|
119
119
|
export * from "./src/page_components/listing/listing_sidebar/events/DownloadLotteryResults"
|
|
120
120
|
export * from "./src/page_components/listing/listing_sidebar/events/EventSection"
|
|
121
|
+
export * from "./src/page_components/sign-in/FormTerms"
|
|
121
122
|
export * from "./src/page_components/sign-in/FormSignIn"
|
|
122
123
|
export * from "./src/page_components/sign-in/FormSignInMFAType"
|
|
123
124
|
export * from "./src/page_components/sign-in/FormSignInMFACode"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bloom-housing/ui-components",
|
|
3
|
-
"version": "4.2.1-alpha.
|
|
3
|
+
"version": "4.2.1-alpha.1",
|
|
4
4
|
"author": "Sean Albert <sean.albert@exygy.com>",
|
|
5
5
|
"description": "Shared user interface components for Bloom affordable housing system",
|
|
6
6
|
"homepage": "https://github.com/bloom-housing/bloom/tree/master/shared/ui-components",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"webpack": "^4.44.2"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@bloom-housing/backend-core": "^4.2.1-alpha.
|
|
72
|
+
"@bloom-housing/backend-core": "^4.2.1-alpha.1",
|
|
73
73
|
"@mapbox/mapbox-sdk": "^0.13.0",
|
|
74
74
|
"@types/body-scroll-lock": "^2.6.1",
|
|
75
75
|
"@types/jwt-decode": "^2.2.1",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"tailwindcss": "2.2.10",
|
|
101
101
|
"typesafe-actions": "^5.1.0"
|
|
102
102
|
},
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "a6f7d7cd4b295500aff5be357fd77c360e13af28"
|
|
104
104
|
}
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
useEffect,
|
|
30
30
|
useMemo,
|
|
31
31
|
useReducer,
|
|
32
|
+
useCallback,
|
|
32
33
|
} from "react"
|
|
33
34
|
import qs from "qs"
|
|
34
35
|
import axiosStatic from "axios"
|
|
@@ -51,6 +52,7 @@ type ContextProps = {
|
|
|
51
52
|
reservedCommunityTypeService: ReservedCommunityTypesService
|
|
52
53
|
unitPriorityService: UnitAccessibilityPriorityTypesService
|
|
53
54
|
unitTypesService: UnitTypesService
|
|
55
|
+
loadProfile: (redirect?: string) => void
|
|
54
56
|
login: (
|
|
55
57
|
email: string,
|
|
56
58
|
password: string,
|
|
@@ -99,7 +101,7 @@ const saveToken = createAction("SAVE_TOKEN")<{
|
|
|
99
101
|
accessToken: string
|
|
100
102
|
dispatch: DispatchType
|
|
101
103
|
}>()
|
|
102
|
-
const saveProfile = createAction("SAVE_PROFILE")<User>()
|
|
104
|
+
const saveProfile = createAction("SAVE_PROFILE")<User | null>()
|
|
103
105
|
const startLoading = createAction("START_LOADING")()
|
|
104
106
|
const stopLoading = createAction("STOP_LOADING")()
|
|
105
107
|
const signOut = createAction("SIGN_OUT")()
|
|
@@ -218,23 +220,30 @@ export const AuthProvider: FunctionComponent = ({ children }) => {
|
|
|
218
220
|
}
|
|
219
221
|
}, [apiUrl, storageType])
|
|
220
222
|
|
|
223
|
+
const loadProfile = useCallback(
|
|
224
|
+
async (redirect?: string) => {
|
|
225
|
+
try {
|
|
226
|
+
const profile = await userService?.userControllerProfile()
|
|
227
|
+
if (profile) {
|
|
228
|
+
dispatch(saveProfile(profile))
|
|
229
|
+
}
|
|
230
|
+
} finally {
|
|
231
|
+
dispatch(stopLoading())
|
|
232
|
+
|
|
233
|
+
if (redirect) {
|
|
234
|
+
router.push(redirect)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
[userService, router]
|
|
239
|
+
)
|
|
240
|
+
|
|
221
241
|
// Load our profile as soon as we have an access token available
|
|
222
242
|
useEffect(() => {
|
|
223
243
|
if (!state.profile && state.accessToken && !state.loading) {
|
|
224
|
-
const loadProfile = async () => {
|
|
225
|
-
dispatch(startLoading())
|
|
226
|
-
try {
|
|
227
|
-
const profile = await userService?.userControllerProfile()
|
|
228
|
-
if (profile) {
|
|
229
|
-
dispatch(saveProfile(profile))
|
|
230
|
-
}
|
|
231
|
-
} finally {
|
|
232
|
-
dispatch(stopLoading())
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
244
|
void loadProfile()
|
|
236
245
|
}
|
|
237
|
-
}, [state.profile, state.accessToken, apiUrl, userService, state.loading])
|
|
246
|
+
}, [state.profile, state.accessToken, apiUrl, userService, state.loading, loadProfile])
|
|
238
247
|
|
|
239
248
|
const contextValues: ContextProps = {
|
|
240
249
|
amiChartsService: new AmiChartsService(),
|
|
@@ -254,6 +263,7 @@ export const AuthProvider: FunctionComponent = ({ children }) => {
|
|
|
254
263
|
accessToken: state.accessToken,
|
|
255
264
|
initialStateLoaded: state.initialStateLoaded,
|
|
256
265
|
profile: state.profile,
|
|
266
|
+
loadProfile,
|
|
257
267
|
login: async (
|
|
258
268
|
email,
|
|
259
269
|
password,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { FunctionComponent, useContext, useEffect } from "react"
|
|
1
|
+
import React, { FunctionComponent, useContext, useEffect, useState } from "react"
|
|
2
2
|
import { clearSiteAlertMessage, setSiteAlertMessage } from "../notifications/SiteAlert"
|
|
3
3
|
import { NavigationContext } from "../config/NavigationContext"
|
|
4
4
|
import { AuthContext } from "./AuthContext"
|
|
@@ -12,6 +12,7 @@ type XOR<T, U> = T | U extends Record<string, unknown>
|
|
|
12
12
|
type RequireLoginProps = {
|
|
13
13
|
signInPath: string
|
|
14
14
|
signInMessage: string
|
|
15
|
+
termsPath?: string // partners portal required accepted terms after sign-in
|
|
15
16
|
} & XOR<{ requireForRoutes?: string[] }, { skipForRoutes: string[] }>
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -24,10 +25,12 @@ const RequireLogin: FunctionComponent<RequireLoginProps> = ({
|
|
|
24
25
|
children,
|
|
25
26
|
signInPath,
|
|
26
27
|
signInMessage,
|
|
28
|
+
termsPath,
|
|
27
29
|
...rest
|
|
28
30
|
}) => {
|
|
29
31
|
const { router } = useContext(NavigationContext)
|
|
30
32
|
const { profile, initialStateLoaded } = useContext(AuthContext)
|
|
33
|
+
const [hasTerms, setHasTerms] = useState(false)
|
|
31
34
|
|
|
32
35
|
// Parse just the pathname portion of the signInPath (in case we want to pass URL params)
|
|
33
36
|
const [signInPathname] = signInPath.split("?")
|
|
@@ -44,6 +47,12 @@ const RequireLogin: FunctionComponent<RequireLoginProps> = ({
|
|
|
44
47
|
? !rest.skipForRoutes.some((path) => new RegExp(path).exec(router.pathname))
|
|
45
48
|
: true)
|
|
46
49
|
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (profile?.jurisdictions?.some((jurisdiction) => jurisdiction.partnerTerms)) {
|
|
52
|
+
setHasTerms(true)
|
|
53
|
+
}
|
|
54
|
+
}, [profile])
|
|
55
|
+
|
|
47
56
|
useEffect(() => {
|
|
48
57
|
if (loginRequiredForPath && initialStateLoaded && !profile) {
|
|
49
58
|
setSiteAlertMessage(signInMessage, "notice")
|
|
@@ -51,9 +60,25 @@ const RequireLogin: FunctionComponent<RequireLoginProps> = ({
|
|
|
51
60
|
} else {
|
|
52
61
|
clearSiteAlertMessage("notice")
|
|
53
62
|
}
|
|
54
|
-
}, [loginRequiredForPath, initialStateLoaded, profile, router, signInPath, signInMessage])
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
if (termsPath && profile && !profile?.agreedToTermsOfService && hasTerms) {
|
|
65
|
+
void router.push(termsPath)
|
|
66
|
+
}
|
|
67
|
+
}, [
|
|
68
|
+
loginRequiredForPath,
|
|
69
|
+
initialStateLoaded,
|
|
70
|
+
profile,
|
|
71
|
+
router,
|
|
72
|
+
signInPath,
|
|
73
|
+
signInMessage,
|
|
74
|
+
termsPath,
|
|
75
|
+
hasTerms,
|
|
76
|
+
])
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
loginRequiredForPath &&
|
|
80
|
+
(!profile || (hasTerms && termsPath && !profile.agreedToTermsOfService))
|
|
81
|
+
) {
|
|
57
82
|
return null
|
|
58
83
|
}
|
|
59
84
|
|
package/src/locales/general.json
CHANGED
|
@@ -499,6 +499,10 @@
|
|
|
499
499
|
"authentication.timeout.action": "Stay logged in",
|
|
500
500
|
"authentication.timeout.signOutMessage": "We care about your security. We logged you out due to inactivity. Please sign in to continue.",
|
|
501
501
|
"authentication.timeout.text": "To protect your identity, your session will expire in one minute due to inactivity. You will lose any unsaved information and be logged out if you choose not to respond.",
|
|
502
|
+
"authentication.terms.termsOfService": "Terms of Service",
|
|
503
|
+
"authentication.terms.reviewToc": "Review Terms of Service",
|
|
504
|
+
"authentication.terms.youMustAcceptToc": "To continue you must accept the Terms of Service",
|
|
505
|
+
"authentication.terms.acceptToc": "I accept the Terms of Service",
|
|
502
506
|
"config.routePrefix": "",
|
|
503
507
|
"errors.agreeError": "You must agree to the terms in order to continue",
|
|
504
508
|
"errors.alert.badRequest": "Looks like something went wrong. Please try again. \n\nContact your housing department if you're still experiencing issues.",
|
|
@@ -843,7 +847,7 @@
|
|
|
843
847
|
"t.none": "None",
|
|
844
848
|
"t.noneFound": "None found.",
|
|
845
849
|
"t.occupancy": "Occupancy",
|
|
846
|
-
"t.ok": "
|
|
850
|
+
"t.ok": "Ok",
|
|
847
851
|
"t.or": "or",
|
|
848
852
|
"t.people": "people",
|
|
849
853
|
"t.perMonth": "per month",
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React, { useContext, useCallback, useMemo } from "react"
|
|
2
|
+
import {
|
|
3
|
+
AuthContext,
|
|
4
|
+
AppearanceStyleType,
|
|
5
|
+
Button,
|
|
6
|
+
Field,
|
|
7
|
+
Form,
|
|
8
|
+
FormCard,
|
|
9
|
+
Icon,
|
|
10
|
+
MarkdownSection,
|
|
11
|
+
t,
|
|
12
|
+
} from "@bloom-housing/ui-components"
|
|
13
|
+
import Markdown from "markdown-to-jsx"
|
|
14
|
+
import { useForm } from "react-hook-form"
|
|
15
|
+
|
|
16
|
+
type FormTermsInValues = {
|
|
17
|
+
agree: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const FormTerms = () => {
|
|
21
|
+
const { profile, userProfileService, loadProfile } = useContext(AuthContext)
|
|
22
|
+
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
24
|
+
const { handleSubmit, register, errors } = useForm<FormTermsInValues>()
|
|
25
|
+
|
|
26
|
+
const onSubmit = useCallback(async () => {
|
|
27
|
+
if (!profile) return
|
|
28
|
+
|
|
29
|
+
const jurisdictionIds =
|
|
30
|
+
profile?.jurisdictions.map((item) => ({
|
|
31
|
+
id: item.id,
|
|
32
|
+
})) || []
|
|
33
|
+
|
|
34
|
+
await userProfileService?.update({
|
|
35
|
+
body: { ...profile, jurisdictions: jurisdictionIds, agreedToTermsOfService: true },
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
loadProfile?.("/")
|
|
39
|
+
}, [loadProfile, profile, userProfileService])
|
|
40
|
+
|
|
41
|
+
const jurisdictionTerms = useMemo(() => {
|
|
42
|
+
const jurisdiction = profile?.jurisdictions.find((jurisdiction) => jurisdiction.partnerTerms)
|
|
43
|
+
return jurisdiction ? jurisdiction.partnerTerms : ""
|
|
44
|
+
}, [profile])
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Form id="terms" className="mt-10" onSubmit={handleSubmit(onSubmit)}>
|
|
48
|
+
<FormCard>
|
|
49
|
+
<div className="form-card__lead text-center">
|
|
50
|
+
<Icon size="2xl" symbol="settings" />
|
|
51
|
+
<h2 className="form-card__title">{t(`authentication.terms.reviewToc`)}</h2>
|
|
52
|
+
<p className="field-note mt-4 text-center">
|
|
53
|
+
{t(`authentication.terms.youMustAcceptToc`)}
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
<div className="overflow-y-auto max-h-96 mt-5 pr-4 text-left">
|
|
57
|
+
{jurisdictionTerms && (
|
|
58
|
+
<MarkdownSection padding={false} fullwidth={true}>
|
|
59
|
+
<Markdown options={{ disableParsingRawHTML: false }}>{jurisdictionTerms}</Markdown>
|
|
60
|
+
</MarkdownSection>
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div className="form-card__group pt-0">
|
|
66
|
+
<Field
|
|
67
|
+
id="agree"
|
|
68
|
+
name="agree"
|
|
69
|
+
type="checkbox"
|
|
70
|
+
className="flex flex-col justify-center items-center"
|
|
71
|
+
label={t(`authentication.terms.acceptToc`)}
|
|
72
|
+
register={register}
|
|
73
|
+
validation={{ required: true }}
|
|
74
|
+
error={!!errors.agree}
|
|
75
|
+
errorMessage={t("errors.agreeError")}
|
|
76
|
+
dataTestId="agree"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div className="border-b" />
|
|
81
|
+
|
|
82
|
+
<div className="form-card__pager">
|
|
83
|
+
<div className="form-card__pager-row primary">
|
|
84
|
+
<Button styleType={AppearanceStyleType.primary} data-test-id="form-submit">
|
|
85
|
+
{t("t.submit")}
|
|
86
|
+
</Button>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</FormCard>
|
|
90
|
+
</Form>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { FormTerms as default, FormTerms }
|
|
@@ -3,16 +3,20 @@ import "./MarkdownSection.scss"
|
|
|
3
3
|
|
|
4
4
|
export interface MarkdownSectionProps {
|
|
5
5
|
fullwidth?: boolean
|
|
6
|
+
padding?: boolean
|
|
6
7
|
children: React.ReactNode
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export const MarkdownSection = (
|
|
10
|
-
const contentWidth =
|
|
10
|
+
export const MarkdownSection = ({ fullwidth, padding = true, children }: MarkdownSectionProps) => {
|
|
11
|
+
const contentWidth = fullwidth ? "markdown" : "markdown max-w-2xl"
|
|
12
|
+
const sectionClassNames = ["markdown-section"]
|
|
13
|
+
|
|
14
|
+
if (padding) sectionClassNames.push("markdown-section--with-padding")
|
|
11
15
|
|
|
12
16
|
return (
|
|
13
|
-
<div className="
|
|
17
|
+
<div className={sectionClassNames.join(" ")}>
|
|
14
18
|
<div className="markdown-section__inner">
|
|
15
|
-
<article className={contentWidth}>{
|
|
19
|
+
<article className={contentWidth}>{children}</article>
|
|
16
20
|
</div>
|
|
17
21
|
</div>
|
|
18
22
|
)
|