@bloom-housing/ui-components 4.4.1-alpha.7 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CHANGELOG.md +949 -0
  2. package/index.ts +8 -6
  3. package/package.json +10 -3
  4. package/src/actions/Button.tsx +2 -2
  5. package/src/actions/ExpandableContent.tsx +9 -5
  6. package/src/blocks/ImageCard.tsx +3 -3
  7. package/src/blocks/StandardCard.docs.mdx +34 -0
  8. package/src/blocks/StandardCard.scss +33 -0
  9. package/src/blocks/StandardCard.tsx +37 -0
  10. package/src/config/index.ts +0 -1
  11. package/src/forms/FieldGroup.tsx +1 -1
  12. package/src/forms/HouseholdSizeField.tsx +2 -1
  13. package/src/global/tables.scss +7 -1
  14. package/src/helpers/formOptions.tsx +0 -9
  15. package/src/helpers/preferences.tsx +3 -314
  16. package/src/icons/Icon.tsx +22 -3
  17. package/src/locales/es.json +18 -0
  18. package/src/locales/general.json +23 -0
  19. package/src/{prototypes → navigation}/SideNav.scss +15 -9
  20. package/src/navigation/SideNav.tsx +39 -0
  21. package/src/notifications/ApplicationStatus.tsx +2 -2
  22. package/src/overlays/Drawer.tsx +1 -1
  23. package/src/overlays/LoadingOverlay.tsx +1 -1
  24. package/src/overlays/Modal.scss +5 -0
  25. package/src/overlays/Modal.tsx +19 -3
  26. package/src/page_components/listing/ListingsGroup.tsx +2 -2
  27. package/src/page_components/listing/listing_sidebar/ExpandableSection.tsx +34 -0
  28. package/src/page_components/listing/listing_sidebar/QuantityRowSection.tsx +1 -0
  29. package/src/page_components/sign-in/FormSignInMFACode.tsx +7 -3
  30. package/src/page_components/sign-in/FormSignInMFAType.tsx +7 -8
  31. package/src/page_components/sign-in/FormTerms.tsx +9 -27
  32. package/src/{global/vendor → tables}/AgPagination.tsx +5 -1
  33. package/src/tables/AgTable.tsx +242 -0
  34. package/src/tables/StandardTable.tsx +17 -4
  35. package/src/authentication/AuthContext.ts +0 -386
  36. package/src/authentication/RequireLogin.tsx +0 -89
  37. package/src/authentication/index.ts +0 -5
  38. package/src/authentication/timeout.tsx +0 -128
  39. package/src/authentication/token.ts +0 -17
  40. package/src/authentication/useRequireLoggedInUser.ts +0 -19
  41. package/src/config/ConfigContext.tsx +0 -36
  42. package/src/helpers/tableSummaries.tsx +0 -104
  43. package/src/notifications/index.ts +0 -4
  44. package/src/page_components/listing/UnitTables.tsx +0 -122
  45. package/src/page_components/listing/listing_sidebar/WhatToExpect.tsx +0 -22
  46. package/src/prototypes/SideNav.tsx +0 -14
@@ -1,386 +0,0 @@
1
- import {
2
- ApplicationsService,
3
- ApplicationFlaggedSetsService,
4
- AuthService,
5
- ListingsService,
6
- User,
7
- UserBasic,
8
- UserCreate,
9
- UserService,
10
- UserProfileService,
11
- serviceOptions,
12
- Status,
13
- AmiChartsService,
14
- ReservedCommunityTypesService,
15
- UnitAccessibilityPriorityTypesService,
16
- UnitTypesService,
17
- PreferencesService,
18
- JurisdictionsService,
19
- ProgramsService,
20
- RequestMfaCodeResponse,
21
- EnumRequestMfaCodeMfaType,
22
- EnumLoginMfaType,
23
- } from "@bloom-housing/backend-core/types"
24
- import {
25
- createContext,
26
- createElement,
27
- FunctionComponent,
28
- useContext,
29
- useEffect,
30
- useMemo,
31
- useReducer,
32
- useCallback,
33
- } from "react"
34
- import qs from "qs"
35
- import axiosStatic from "axios"
36
- import { ConfigContext } from "../config/ConfigContext"
37
- import { createAction, createReducer } from "typesafe-actions"
38
- import { clearToken, getToken, getTokenTtl, setToken } from "./token"
39
- import { NavigationContext } from "../config/NavigationContext"
40
-
41
- type ContextProps = {
42
- amiChartsService: AmiChartsService
43
- applicationsService: ApplicationsService
44
- applicationFlaggedSetsService: ApplicationFlaggedSetsService
45
- listingsService: ListingsService
46
- jurisdictionsService: JurisdictionsService
47
- userService: UserService
48
- userProfileService: UserProfileService
49
- authService: AuthService
50
- preferencesService: PreferencesService
51
- programsService: ProgramsService
52
- reservedCommunityTypeService: ReservedCommunityTypesService
53
- unitPriorityService: UnitAccessibilityPriorityTypesService
54
- unitTypesService: UnitTypesService
55
- loadProfile: (redirect?: string) => void
56
- login: (
57
- email: string,
58
- password: string,
59
- mfaCode?: string,
60
- mfaType?: EnumLoginMfaType
61
- ) => Promise<User | undefined>
62
- loginWithToken: (token: string) => Promise<User | undefined>
63
- resetPassword: (
64
- token: string,
65
- password: string,
66
- passwordConfirmation: string
67
- ) => Promise<User | undefined>
68
- signOut: () => void
69
- confirmAccount: (token: string) => Promise<User | undefined>
70
- forgotPassword: (email: string) => Promise<string | undefined>
71
- createUser: (user: UserCreate) => Promise<UserBasic | undefined>
72
- resendConfirmation: (email: string) => Promise<Status | undefined>
73
- accessToken?: string
74
- initialStateLoaded: boolean
75
- loading: boolean
76
- profile?: User
77
- requestMfaCode: (
78
- email: string,
79
- password: string,
80
- mfaType: EnumRequestMfaCodeMfaType,
81
- phoneNumber?: string
82
- ) => Promise<RequestMfaCodeResponse | undefined>
83
- }
84
-
85
- // Internal Provider State
86
- type AuthState = {
87
- accessToken?: string
88
- initialStateLoaded: boolean
89
- language?: string
90
- loading: boolean
91
- profile?: User
92
- refreshTimer?: number
93
- storageType: string
94
- }
95
-
96
- type DispatchType = (...arg: [unknown]) => void
97
-
98
- // State Mutation Actions
99
- const saveToken = createAction("SAVE_TOKEN")<{
100
- apiUrl: string
101
- accessToken: string
102
- dispatch: DispatchType
103
- }>()
104
- const saveProfile = createAction("SAVE_PROFILE")<User | null>()
105
- const startLoading = createAction("START_LOADING")()
106
- const stopLoading = createAction("STOP_LOADING")()
107
- const signOut = createAction("SIGN_OUT")()
108
-
109
- const scheduleTokenRefresh = (accessToken: string, onRefresh: (accessToken: string) => void) => {
110
- const ttl = getTokenTtl(accessToken)
111
-
112
- if (ttl < 0) {
113
- // If ttl is negative, then our token is already expired, we'll have to re-login to get a new token.
114
- //dispatch(signOut())
115
- return null
116
- } else {
117
- // Queue up a refresh for ~1 minute before the token expires
118
- return (setTimeout(() => {
119
- const run = async () => {
120
- const reposne = await new AuthService().token()
121
- if (reposne) {
122
- onRefresh(reposne.accessToken)
123
- }
124
- }
125
- void run()
126
- }, Math.max(ttl - 60000, 0)) as unknown) as number
127
- }
128
- }
129
- const reducer = createReducer(
130
- {
131
- loading: false,
132
- initialStateLoaded: false,
133
- storageType: "session",
134
- language: "en",
135
- } as AuthState,
136
- {
137
- SAVE_TOKEN: (state, { payload }) => {
138
- const { refreshTimer: oldRefresh, ...rest } = state
139
- const { accessToken, apiUrl, dispatch } = payload
140
-
141
- // If an existing refresh timer has been defined, remove it as the access token has changed
142
- if (oldRefresh) {
143
- clearTimeout(oldRefresh)
144
- }
145
-
146
- // Save off the token in local storage for persistence across reloads.
147
- setToken(state.storageType, accessToken)
148
-
149
- const refreshTimer = scheduleTokenRefresh(accessToken, (newAccessToken) =>
150
- dispatch(saveToken({ apiUrl, accessToken: newAccessToken, dispatch }))
151
- )
152
- serviceOptions.axios = axiosStatic.create({
153
- baseURL: apiUrl,
154
- headers: {
155
- language: state.language,
156
- jurisdictionName: process.env.jurisdictionName,
157
- ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
158
- },
159
- })
160
-
161
- return {
162
- ...rest,
163
- ...(refreshTimer && { refreshTimer }),
164
- accessToken: accessToken,
165
- }
166
- },
167
- SAVE_PROFILE: (state, { payload: user }) => ({ ...state, profile: user }),
168
- START_LOADING: (state) => ({ ...state, loading: true }),
169
- END_LOADING: (state) => ({ ...state, loading: false }),
170
- SIGN_OUT: ({ storageType }) => {
171
- clearToken(storageType)
172
- // Clear out all existing state other than the storage type
173
- return { loading: false, storageType, initialStateLoaded: true }
174
- },
175
- }
176
- )
177
-
178
- export const AuthContext = createContext<Partial<ContextProps>>({})
179
- export const AuthProvider: FunctionComponent = ({ children }) => {
180
- const { apiUrl, storageType } = useContext(ConfigContext)
181
- const { router } = useContext(NavigationContext)
182
- const [state, dispatch] = useReducer(reducer, {
183
- loading: false,
184
- initialStateLoaded: false,
185
- storageType,
186
- language: router.locale,
187
- })
188
-
189
- const userService = useMemo(() => new UserService(), [])
190
- const authService = new AuthService()
191
-
192
- useEffect(() => {
193
- serviceOptions.axios = axiosStatic.create({
194
- baseURL: apiUrl,
195
- headers: {
196
- language: router.locale,
197
- jurisdictionName: process.env.jurisdictionName,
198
- appUrl: window.location.origin,
199
- ...(state.accessToken && { Authorization: `Bearer ${state.accessToken}` }),
200
- },
201
- paramsSerializer: (params) => {
202
- return qs.stringify(params)
203
- },
204
- })
205
- }, [router, apiUrl, state.accessToken, router.locale])
206
-
207
- // On initial load/reload, check localStorage to see if we have a token available
208
- useEffect(() => {
209
- const accessToken = getToken(storageType)
210
- if (accessToken) {
211
- const ttl = getTokenTtl(accessToken)
212
-
213
- if (ttl > 0) {
214
- dispatch(saveToken({ accessToken, apiUrl, dispatch }))
215
- } else {
216
- dispatch(signOut())
217
- }
218
- } else {
219
- dispatch(signOut())
220
- }
221
- }, [apiUrl, storageType])
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
-
241
- // Load our profile as soon as we have an access token available
242
- useEffect(() => {
243
- if (!state.profile && state.accessToken && !state.loading) {
244
- void loadProfile()
245
- }
246
- }, [state.profile, state.accessToken, apiUrl, userService, state.loading, loadProfile])
247
-
248
- const contextValues: ContextProps = {
249
- amiChartsService: new AmiChartsService(),
250
- applicationsService: new ApplicationsService(),
251
- applicationFlaggedSetsService: new ApplicationFlaggedSetsService(),
252
- listingsService: new ListingsService(),
253
- jurisdictionsService: new JurisdictionsService(),
254
- userService: new UserService(),
255
- userProfileService: new UserProfileService(),
256
- authService: new AuthService(),
257
- preferencesService: new PreferencesService(),
258
- programsService: new ProgramsService(),
259
- reservedCommunityTypeService: new ReservedCommunityTypesService(),
260
- unitPriorityService: new UnitAccessibilityPriorityTypesService(),
261
- unitTypesService: new UnitTypesService(),
262
- loading: state.loading,
263
- accessToken: state.accessToken,
264
- initialStateLoaded: state.initialStateLoaded,
265
- profile: state.profile,
266
- loadProfile,
267
- login: async (
268
- email,
269
- password,
270
- mfaCode: string | undefined = undefined,
271
- mfaType: EnumLoginMfaType | undefined = undefined
272
- ) => {
273
- dispatch(signOut())
274
- dispatch(startLoading())
275
- try {
276
- const response = await authService?.login({ body: { email, password, mfaCode, mfaType } })
277
- if (response) {
278
- dispatch(saveToken({ accessToken: response.accessToken, apiUrl, dispatch }))
279
- const profile = await userService?.userControllerProfile()
280
- if (profile) {
281
- dispatch(saveProfile(profile))
282
- return profile
283
- }
284
- }
285
- return undefined
286
- } finally {
287
- dispatch(stopLoading())
288
- }
289
- },
290
- loginWithToken: async (token: string) => {
291
- dispatch(saveToken({ accessToken: token, apiUrl, dispatch }))
292
- const profile = await userService?.userControllerProfile()
293
- if (profile) {
294
- dispatch(saveProfile(profile))
295
- return profile
296
- }
297
-
298
- return undefined
299
- },
300
- signOut: () => dispatch(signOut()),
301
- resetPassword: async (token, password, passwordConfirmation) => {
302
- dispatch(startLoading())
303
- try {
304
- const response = await userService?.updatePassword({
305
- body: {
306
- token,
307
- password,
308
- passwordConfirmation,
309
- },
310
- })
311
- if (response) {
312
- dispatch(saveToken({ accessToken: response.accessToken, apiUrl, dispatch }))
313
- const profile = await userService?.userControllerProfile()
314
- if (profile) {
315
- dispatch(saveProfile(profile))
316
- return profile
317
- }
318
- }
319
- return undefined
320
- } finally {
321
- dispatch(stopLoading())
322
- }
323
- },
324
- confirmAccount: async (token) => {
325
- dispatch(startLoading())
326
- try {
327
- const response = await userService?.confirm({ body: { token } })
328
- if (response) {
329
- dispatch(saveToken({ accessToken: response.accessToken, apiUrl, dispatch }))
330
- const profile = await userService?.userControllerProfile()
331
- if (profile) {
332
- dispatch(saveProfile(profile))
333
- return profile
334
- }
335
- }
336
- return undefined
337
- } finally {
338
- dispatch(stopLoading())
339
- }
340
- },
341
- createUser: async (user: UserCreate) => {
342
- dispatch(startLoading())
343
- try {
344
- const response = await userService?.create({
345
- body: { ...user, appUrl: window.location.origin },
346
- })
347
- return response
348
- } finally {
349
- dispatch(stopLoading())
350
- }
351
- },
352
- resendConfirmation: async (email: string) => {
353
- dispatch(startLoading())
354
- try {
355
- const response = await userService?.resendConfirmation({
356
- body: { email, appUrl: window.location.origin },
357
- })
358
- return response
359
- } finally {
360
- dispatch(stopLoading())
361
- }
362
- },
363
- forgotPassword: async (email) => {
364
- dispatch(startLoading())
365
- try {
366
- const response = await userService?.forgotPassword({
367
- body: { email, appUrl: window.location.origin },
368
- })
369
- return response?.message
370
- } finally {
371
- dispatch(stopLoading())
372
- }
373
- },
374
- requestMfaCode: async (email, password, mfaType, phoneNumber) => {
375
- dispatch(startLoading())
376
- try {
377
- return await authService?.requestMfaCode({
378
- body: { email, password, mfaType, phoneNumber },
379
- })
380
- } finally {
381
- dispatch(stopLoading())
382
- }
383
- },
384
- }
385
- return createElement(AuthContext.Provider, { value: contextValues }, children)
386
- }
@@ -1,89 +0,0 @@
1
- import React, { FunctionComponent, useContext, useEffect, useState } from "react"
2
- import { clearSiteAlertMessage, setSiteAlertMessage } from "../notifications/SiteAlert"
3
- import { NavigationContext } from "../config/NavigationContext"
4
- import { AuthContext } from "./AuthContext"
5
-
6
- // See https://github.com/Microsoft/TypeScript/issues/14094
7
- type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never }
8
- type XOR<T, U> = T | U extends Record<string, unknown>
9
- ? (Without<T, U> & U) | (Without<U, T> & T)
10
- : T | U
11
-
12
- type RequireLoginProps = {
13
- signInPath: string
14
- signInMessage: string
15
- termsPath?: string // partners portal required accepted terms after sign-in
16
- } & XOR<{ requireForRoutes?: string[] }, { skipForRoutes: string[] }>
17
-
18
- /**
19
- * Require a login to render children. Will redirect to `signInPath` if not logged in.
20
- *
21
- * Props can be specified with either a "whitelist" (list of routes to skip check for) or a "blacklist" (list of
22
- * routes to apply test on). If no list of routes is provided, then will always apply check.
23
- */
24
- const RequireLogin: FunctionComponent<RequireLoginProps> = ({
25
- children,
26
- signInPath,
27
- signInMessage,
28
- termsPath,
29
- ...rest
30
- }) => {
31
- const { router } = useContext(NavigationContext)
32
- const { profile, initialStateLoaded } = useContext(AuthContext)
33
- const [hasTerms, setHasTerms] = useState(false)
34
-
35
- // Parse just the pathname portion of the signInPath (in case we want to pass URL params)
36
- const [signInPathname] = signInPath.split("?")
37
-
38
- // Check if this route requires a login or not (can be specified as a whitelist or a blacklist).
39
- const loginRequiredForPath =
40
- // by definition, we shouldn't require login on the sign in page itself
41
- router.pathname !== signInPathname &&
42
- ("requireForRoutes" in rest
43
- ? rest.requireForRoutes
44
- ? rest.requireForRoutes.some((path) => new RegExp(path).exec(router.pathname))
45
- : true
46
- : rest.skipForRoutes
47
- ? !rest.skipForRoutes.some((path) => new RegExp(path).exec(router.pathname))
48
- : true)
49
-
50
- useEffect(() => {
51
- if (profile?.jurisdictions?.some((jurisdiction) => jurisdiction.partnerTerms)) {
52
- setHasTerms(true)
53
- }
54
- }, [profile])
55
-
56
- useEffect(() => {
57
- if (loginRequiredForPath && initialStateLoaded && !profile) {
58
- setSiteAlertMessage(signInMessage, "notice")
59
- void router.push(signInPath)
60
- } else {
61
- clearSiteAlertMessage("notice")
62
- }
63
-
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
- ) {
82
- return null
83
- }
84
-
85
- // Login either isn't required, or the user object is loaded successfully, continue rendering as normal.
86
- return <>{children}</>
87
- }
88
-
89
- export { RequireLogin as default, RequireLogin }
@@ -1,5 +0,0 @@
1
- export { AuthContext, AuthProvider } from "./AuthContext"
2
- export { RequireLogin } from "./RequireLogin"
3
- export { useRequireLoggedInUser } from "./useRequireLoggedInUser"
4
- export { ACCESS_TOKEN_LOCAL_STORAGE_KEY } from "./token"
5
- export { IdleTimeout, LoggedInUserIdleTimeout } from "./timeout"
@@ -1,128 +0,0 @@
1
- import React, { createElement, FunctionComponent, useContext, useEffect, useState } from "react"
2
- import { AuthContext } from "./AuthContext"
3
- import { ConfigContext, NavigationContext } from "../config"
4
- import { Button } from "../actions/Button"
5
- import { Modal } from "../overlays/Modal"
6
- import { setSiteAlertMessage } from "../notifications/SiteAlert"
7
- import { AlertTypes } from "../notifications/alertTypes"
8
- import { t } from "../helpers/translator"
9
- import { AppearanceStyleType } from "../global/AppearanceTypes"
10
-
11
- const PROMPT_TIMEOUT = 60000
12
- const events = ["mousemove", "keypress", "scroll"]
13
-
14
- function useIdleTimeout(timeoutMs: number, onTimeout: () => void) {
15
- useEffect(() => {
16
- let timer: number
17
- const restartTimer = () => {
18
- if (timer) {
19
- clearTimeout(timer)
20
- }
21
- timer = (setTimeout(onTimeout, timeoutMs) as unknown) as number
22
- }
23
-
24
- // Listen for any activity events & reset the timer when they are found
25
- if (typeof document !== "undefined") {
26
- events.forEach((event) => document.addEventListener(event, restartTimer, false))
27
- }
28
-
29
- // Clean up our listeners & clear the timeout on unmounting/updating the effect
30
- return () => {
31
- if (timer) {
32
- clearTimeout(timer)
33
- }
34
- events.forEach((event) => document.removeEventListener(event, restartTimer, false))
35
- }
36
- }, [timeoutMs, onTimeout])
37
- }
38
-
39
- type IdleTimeoutProps = {
40
- promptTitle: string
41
- promptText: string
42
- promptAction: string
43
- redirectPath: string
44
- alertMessage: string
45
- alertType?: AlertTypes
46
- onTimeout: () => unknown
47
- }
48
-
49
- export const IdleTimeout: FunctionComponent<IdleTimeoutProps> = ({
50
- promptTitle,
51
- promptAction,
52
- promptText,
53
- redirectPath,
54
- alertMessage,
55
- alertType = "alert",
56
- onTimeout,
57
- }) => {
58
- const { idleTimeout } = useContext(ConfigContext)
59
- const [promptTimeout, setPromptTimeout] = useState<number | undefined>()
60
- const { router } = useContext(NavigationContext)
61
-
62
- useIdleTimeout(idleTimeout, () => {
63
- // Clear any existing prompt timeouts
64
- if (promptTimeout) {
65
- clearTimeout(promptTimeout)
66
- }
67
-
68
- // Give the user 1 minute to respond to the prompt before the onTimeout action
69
- setPromptTimeout(
70
- (setTimeout(() => {
71
- const timeoutAction = async () => {
72
- setPromptTimeout(undefined)
73
- await onTimeout()
74
- setSiteAlertMessage(alertMessage, alertType)
75
- void router.push(redirectPath)
76
- }
77
- void timeoutAction()
78
- }, PROMPT_TIMEOUT) as unknown) as number
79
- )
80
- })
81
-
82
- const modalActions = [
83
- <Button
84
- styleType={AppearanceStyleType.primary}
85
- onClick={() => {
86
- clearTimeout(promptTimeout)
87
- setPromptTimeout(undefined)
88
- }}
89
- >
90
- {promptAction}
91
- </Button>,
92
- ]
93
-
94
- return (
95
- <Modal
96
- open={Boolean(promptTimeout)}
97
- title={promptTitle}
98
- ariaDescription={promptText}
99
- actions={modalActions}
100
- hideCloseIcon
101
- role="alertdialog"
102
- >
103
- {promptText}
104
- </Modal>
105
- )
106
- }
107
-
108
- export const LoggedInUserIdleTimeout = ({ onTimeout }: { onTimeout?: () => unknown }) => {
109
- const { profile, signOut } = useContext(AuthContext)
110
-
111
- const timeoutFxn = async () => {
112
- onTimeout && (await onTimeout())
113
- signOut && signOut()
114
- }
115
-
116
- // Only render the IdleTimeout component if the user is logged in
117
- return profile && signOut
118
- ? createElement(IdleTimeout, {
119
- promptTitle: t("t.areYouStillWorking"),
120
- promptText: t("authentication.timeout.text"),
121
- promptAction: t("authentication.timeout.action"),
122
- redirectPath: `/sign-in`,
123
- alertMessage: t("authentication.timeout.signOutMessage"),
124
- alertType: "notice",
125
- onTimeout: timeoutFxn,
126
- })
127
- : null
128
- }
@@ -1,17 +0,0 @@
1
- import jwtDecode from "jwt-decode"
2
-
3
- export const ACCESS_TOKEN_LOCAL_STORAGE_KEY = "@bht"
4
-
5
- const getStorage = (type: string) => (type === "local" ? localStorage : sessionStorage)
6
-
7
- export const setToken = (storageType: string, token: string) =>
8
- getStorage(storageType).setItem(ACCESS_TOKEN_LOCAL_STORAGE_KEY, token)
9
- export const getToken = (storageType: string) =>
10
- getStorage(storageType).getItem(ACCESS_TOKEN_LOCAL_STORAGE_KEY)
11
- export const clearToken = (storageType: string) =>
12
- getStorage(storageType).removeItem(ACCESS_TOKEN_LOCAL_STORAGE_KEY)
13
-
14
- export const getTokenTtl = (token: string) => {
15
- const { exp = 0 } = jwtDecode(token)
16
- return new Date(exp * 1000).valueOf() - new Date().valueOf()
17
- }
@@ -1,19 +0,0 @@
1
- import { useContext } from "react"
2
- import { AuthContext } from "./AuthContext"
3
- import { NavigationContext } from "../config/NavigationContext"
4
-
5
- /**
6
- * Require a logged in user. Waits on initial load, then initiates a redirect to `redirectPath` if user is not
7
- * logged in.
8
- */
9
- function useRequireLoggedInUser(redirectPath: string) {
10
- const { profile, initialStateLoaded } = useContext(AuthContext)
11
- const { router } = useContext(NavigationContext)
12
-
13
- if (initialStateLoaded && !profile) {
14
- void router.push(redirectPath)
15
- }
16
- return profile
17
- }
18
-
19
- export { useRequireLoggedInUser as default, useRequireLoggedInUser }
@@ -1,36 +0,0 @@
1
- import { createContext, createElement, FunctionComponent } from "react"
2
-
3
- type ConfigContextProps = {
4
- storageType: "local" | "session"
5
- apiUrl: string
6
- idleTimeout: number
7
- }
8
-
9
- const timeoutMinutes = parseInt(process.env.idleTimeout || process.env.IDLE_TIMEOUT || "5")
10
- const defaultTimeout = timeoutMinutes * 60 * 1000
11
-
12
- export const ConfigContext = createContext<ConfigContextProps>({
13
- storageType: "session",
14
- apiUrl: "",
15
- idleTimeout: defaultTimeout,
16
- })
17
-
18
- export const ConfigProvider: FunctionComponent<{
19
- apiUrl: string
20
- storageType?: ConfigContextProps["storageType"]
21
- idleTimeout?: number
22
- }> = ({ apiUrl, storageType = "session", idleTimeout = defaultTimeout, children }) => {
23
- return createElement(
24
- ConfigContext.Provider,
25
- {
26
- value: {
27
- apiUrl,
28
- storageType,
29
- idleTimeout,
30
- },
31
- },
32
- children
33
- )
34
- }
35
-
36
- export default ConfigContext