Skip to content

Commit 9f0e0ee

Browse files
committed
Create dashboard provider
1 parent 528892f commit 9f0e0ee

File tree

16 files changed

+167
-229
lines changed

16 files changed

+167
-229
lines changed

site/src/AppRouter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import UsersPage from "pages/UsersPage/UsersPage"
1212
import WorkspacesPage from "pages/WorkspacesPage/WorkspacesPage"
1313
import { FC, lazy, Suspense } from "react"
1414
import { Route, Routes, BrowserRouter as Router } from "react-router-dom"
15-
import { DashboardLayout } from "./components/DashboardLayout/DashboardLayout"
15+
import { DashboardLayout } from "./components/Dashboard/DashboardLayout"
1616
import { RequireAuth } from "./components/RequireAuth/RequireAuth"
1717
import { SettingsLayout } from "./components/SettingsLayout/SettingsLayout"
1818
import { DeploySettingsLayout } from "components/DeploySettingsLayout/DeploySettingsLayout"

site/src/components/DashboardLayout/DashboardLayout.tsx renamed to site/src/components/Dashboard/DashboardLayout.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ServiceBanner } from "components/ServiceBanner/ServiceBanner"
1111
import { updateCheckMachine } from "xServices/updateCheck/updateCheckXService"
1212
import { usePermissions } from "hooks/usePermissions"
1313
import { UpdateCheckResponse } from "api/typesGenerated"
14+
import { DashboardProvider } from "./DashboardProvider"
1415

1516
export const DashboardLayout: FC = () => {
1617
const styles = useStyles()
@@ -23,7 +24,7 @@ export const DashboardLayout: FC = () => {
2324
const { error: updateCheckError, updateCheck } = updateCheckState.context
2425

2526
return (
26-
<>
27+
<DashboardProvider>
2728
<ServiceBanner />
2829
<LicenseBanner />
2930

@@ -50,7 +51,7 @@ export const DashboardLayout: FC = () => {
5051
</Suspense>
5152
</div>
5253
</div>
53-
</>
54+
</DashboardProvider>
5455
)
5556
}
5657

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { useMachine } from "@xstate/react"
2+
import {
3+
AppearanceConfig,
4+
BuildInfoResponse,
5+
Entitlements,
6+
} from "api/typesGenerated"
7+
import { FullScreenLoader } from "components/Loader/FullScreenLoader"
8+
import { createContext, FC, PropsWithChildren, useContext } from "react"
9+
import { appearanceMachine } from "xServices/appearance/appearanceXService"
10+
import { buildInfoMachine } from "xServices/buildInfo/buildInfoXService"
11+
import { entitlementsMachine } from "xServices/entitlements/entitlementsXService"
12+
13+
interface Appearance {
14+
config: AppearanceConfig
15+
preview: boolean
16+
setPreview: (config: AppearanceConfig) => void
17+
save: (config: AppearanceConfig) => void
18+
}
19+
20+
interface DashboardProviderValue {
21+
buildInfo: BuildInfoResponse
22+
entitlements: Entitlements
23+
appearance: Appearance
24+
}
25+
26+
export const DashboardProviderContext = createContext<
27+
DashboardProviderValue | undefined
28+
>(undefined)
29+
30+
export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
31+
const [buildInfoState] = useMachine(buildInfoMachine)
32+
const [entitlementsState] = useMachine(entitlementsMachine)
33+
const [appearanceState, appearanceSend] = useMachine(appearanceMachine)
34+
const { buildInfo } = buildInfoState.context
35+
const { entitlements } = entitlementsState.context
36+
const { appearance, preview } = appearanceState.context
37+
const isLoading = !buildInfo || !entitlements || !appearance
38+
39+
const setAppearancePreview = (config: AppearanceConfig) => {
40+
appearanceSend({
41+
type: "SET_PREVIEW_APPEARANCE",
42+
appearance: config,
43+
})
44+
}
45+
46+
const saveAppearance = (config: AppearanceConfig) => {
47+
appearanceSend({
48+
type: "SAVE_APPEARANCE",
49+
appearance: config,
50+
})
51+
}
52+
53+
if (isLoading) {
54+
return <FullScreenLoader />
55+
}
56+
57+
return (
58+
<DashboardProviderContext.Provider
59+
value={{
60+
buildInfo,
61+
entitlements,
62+
appearance: {
63+
preview,
64+
config: appearance,
65+
setPreview: setAppearancePreview,
66+
save: saveAppearance,
67+
},
68+
}}
69+
>
70+
{children}
71+
</DashboardProviderContext.Provider>
72+
)
73+
}
74+
75+
export const useDashboard = (): DashboardProviderValue => {
76+
const context = useContext(DashboardProviderContext)
77+
78+
if (!context) {
79+
throw new Error("useDashboard only can be used inside of DashboardProvider")
80+
}
81+
82+
return context
83+
}

site/src/components/LicenseBanner/LicenseBanner.tsx

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
1-
import { useActor } from "@xstate/react"
2-
import { useContext, useEffect } from "react"
3-
import { XServiceContext } from "xServices/StateContext"
1+
import { useDashboard } from "components/Dashboard/DashboardProvider"
42
import { LicenseBannerView } from "./LicenseBannerView"
53

64
export const LicenseBanner: React.FC = () => {
7-
const xServices = useContext(XServiceContext)
8-
const [entitlementsState, entitlementsSend] = useActor(
9-
xServices.entitlementsXService,
10-
)
11-
const { errors, warnings } = entitlementsState.context.entitlements
12-
13-
/** Gets license data on app mount because LicenseBanner is mounted in App */
14-
useEffect(() => {
15-
entitlementsSend("GET_ENTITLEMENTS")
16-
}, [entitlementsSend])
5+
const { entitlements } = useDashboard()
6+
const { errors, warnings } = entitlements
177

188
if (errors.length > 0 || warnings.length > 0) {
199
return <LicenseBannerView errors={errors} warnings={warnings} />

site/src/components/Navbar/Navbar.tsx

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
1-
import { shallowEqual, useActor, useSelector } from "@xstate/react"
21
import { useAuth } from "components/AuthProvider/AuthProvider"
2+
import { useDashboard } from "components/Dashboard/DashboardProvider"
3+
import { useFeatureVisibility } from "hooks/useFeatureVisibility"
34
import { useMe } from "hooks/useMe"
45
import { usePermissions } from "hooks/usePermissions"
5-
import { useContext, FC } from "react"
6-
import { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors"
7-
import { XServiceContext } from "../../xServices/StateContext"
6+
import { FC } from "react"
87
import { NavbarView } from "../NavbarView/NavbarView"
98

109
export const Navbar: FC = () => {
11-
const xServices = useContext(XServiceContext)
12-
const [appearanceState] = useActor(xServices.appearanceXService)
13-
const [buildInfoState] = useActor(xServices.buildInfoXService)
10+
const { appearance, buildInfo } = useDashboard()
1411
const [_, authSend] = useAuth()
1512
const me = useMe()
1613
const permissions = usePermissions()
17-
const featureVisibility = useSelector(
18-
xServices.entitlementsXService,
19-
selectFeatureVisibility,
20-
shallowEqual,
21-
)
14+
const featureVisibility = useFeatureVisibility()
2215
const canViewAuditLog =
2316
featureVisibility["audit_log"] && Boolean(permissions.viewAuditLog)
2417
const canViewDeployment = Boolean(permissions.viewDeploymentConfig)
@@ -27,8 +20,8 @@ export const Navbar: FC = () => {
2720
return (
2821
<NavbarView
2922
user={me}
30-
logo_url={appearanceState.context.appearance.logo_url}
31-
buildInfo={buildInfoState.context.buildInfo}
23+
logo_url={appearance.config.logo_url}
24+
buildInfo={buildInfo}
3225
onSignOut={onSignOut}
3326
canViewAuditLog={canViewAuditLog}
3427
canViewDeployment={canViewDeployment}

site/src/components/ServiceBanner/ServiceBanner.tsx

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,10 @@
1-
import { useActor } from "@xstate/react"
2-
import { useAuth } from "components/AuthProvider/AuthProvider"
3-
import { useContext, useEffect } from "react"
4-
import { XServiceContext } from "xServices/StateContext"
1+
import { useDashboard } from "components/Dashboard/DashboardProvider"
52
import { ServiceBannerView } from "./ServiceBannerView"
63

74
export const ServiceBanner: React.FC = () => {
8-
const xServices = useContext(XServiceContext)
9-
const [appearanceState, appearanceSend] = useActor(
10-
xServices.appearanceXService,
11-
)
12-
const [authState] = useAuth()
5+
const { appearance } = useDashboard()
136
const { message, background_color, enabled } =
14-
appearanceState.context.appearance.service_banner
15-
16-
useEffect(() => {
17-
if (authState.matches("signedIn")) {
18-
appearanceSend("GET_APPEARANCE")
19-
}
20-
}, [appearanceSend, authState])
7+
appearance.config.service_banner
218

229
if (!enabled) {
2310
return null
@@ -28,7 +15,7 @@ export const ServiceBanner: React.FC = () => {
2815
<ServiceBannerView
2916
message={message}
3017
backgroundColor={background_color}
31-
preview={appearanceState.context.preview}
18+
preview={appearance.preview}
3219
/>
3320
)
3421
} else {
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import { useSelector } from "@xstate/react"
21
import { FeatureName } from "api/typesGenerated"
3-
import { useContext } from "react"
2+
import { useDashboard } from "components/Dashboard/DashboardProvider"
43
import { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors"
5-
import { XServiceContext } from "xServices/StateContext"
64

75
export const useFeatureVisibility = (): Record<FeatureName, boolean> => {
8-
const xServices = useContext(XServiceContext)
9-
return useSelector(xServices.entitlementsXService, selectFeatureVisibility)
6+
const { entitlements } = useDashboard()
7+
return selectFeatureVisibility(entitlements)
108
}

site/src/pages/DeploySettingsPage/AppearanceSettingsPage/AppearanceSettingsPage.tsx

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,32 @@
1-
import { useActor } from "@xstate/react"
21
import { AppearanceConfig } from "api/typesGenerated"
3-
import { useContext, FC } from "react"
2+
import { useDashboard } from "components/Dashboard/DashboardProvider"
3+
import { FC } from "react"
44
import { Helmet } from "react-helmet-async"
55
import { pageTitle } from "util/page"
6-
import { XServiceContext } from "xServices/StateContext"
76
import { AppearanceSettingsPageView } from "./AppearanceSettingsPageView"
87

98
// ServiceBanner is unlike the other Deployment Settings pages because it
109
// implements a form, whereas the others are read-only. We make this
1110
// exception because the Service Banner is visual, and configuring it from
1211
// the command line would be a significantly worse user experience.
1312
const AppearanceSettingsPage: FC = () => {
14-
const xServices = useContext(XServiceContext)
15-
const [appearanceXService, appearanceSend] = useActor(
16-
xServices.appearanceXService,
17-
)
18-
const [entitlementsState] = useActor(xServices.entitlementsXService)
19-
const appearance = appearanceXService.context.appearance
20-
13+
const { appearance, entitlements } = useDashboard()
2114
const isEntitled =
22-
entitlementsState.context.entitlements.features["appearance"]
23-
.entitlement !== "not_entitled"
15+
entitlements.features["appearance"].entitlement !== "not_entitled"
2416

2517
const updateAppearance = (
2618
newConfig: Partial<AppearanceConfig>,
2719
preview: boolean,
2820
) => {
2921
const newAppearance = {
30-
...appearance,
22+
...appearance.config,
3123
...newConfig,
3224
}
3325
if (preview) {
34-
appearanceSend({
35-
type: "SET_PREVIEW_APPEARANCE",
36-
appearance: newAppearance,
37-
})
26+
appearance.setPreview(newAppearance)
3827
return
3928
}
40-
appearanceSend({
41-
type: "SET_APPEARANCE",
42-
appearance: newAppearance,
43-
})
29+
appearance.save(newAppearance)
4430
}
4531

4632
return (
@@ -50,7 +36,7 @@ const AppearanceSettingsPage: FC = () => {
5036
</Helmet>
5137

5238
<AppearanceSettingsPageView
53-
appearance={appearance}
39+
appearance={appearance.config}
5440
isEntitled={isEntitled}
5541
updateAppearance={updateAppearance}
5642
/>

site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPage.tsx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { useActor } from "@xstate/react"
1+
import { useDashboard } from "components/Dashboard/DashboardProvider"
22
import { useDeploySettings } from "components/DeploySettingsLayout/DeploySettingsLayout"
3-
import { useContext, FC } from "react"
3+
import { FC } from "react"
44
import { Helmet } from "react-helmet-async"
55
import { pageTitle } from "util/page"
6-
import { XServiceContext } from "xServices/StateContext"
76
import { SecuritySettingsPageView } from "./SecuritySettingsPageView"
87

98
const SecuritySettingsPage: FC = () => {
109
const { deploymentConfig: deploymentConfig } = useDeploySettings()
11-
const xServices = useContext(XServiceContext)
12-
const [entitlementsState] = useActor(xServices.entitlementsXService)
10+
const { entitlements } = useDashboard()
1311

1412
return (
1513
<>
@@ -19,12 +17,9 @@ const SecuritySettingsPage: FC = () => {
1917

2018
<SecuritySettingsPageView
2119
deploymentConfig={deploymentConfig}
22-
featureAuditLogEnabled={
23-
entitlementsState.context.entitlements.features["audit_log"].enabled
24-
}
20+
featureAuditLogEnabled={entitlements.features["audit_log"].enabled}
2521
featureBrowserOnlyEnabled={
26-
entitlementsState.context.entitlements.features["browser_only"]
27-
.enabled
22+
entitlements.features["browser_only"].enabled
2823
}
2924
/>
3025
</>

site/src/pages/SetupPage/SetupPage.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import { useActor, useMachine } from "@xstate/react"
2-
import { FC, useContext, useEffect } from "react"
1+
import { useMachine } from "@xstate/react"
2+
import { useAuth } from "components/AuthProvider/AuthProvider"
3+
import { FC, useEffect } from "react"
34
import { Helmet } from "react-helmet-async"
45
import { pageTitle } from "util/page"
56
import { setupMachine } from "xServices/setup/setupXService"
6-
import { XServiceContext } from "xServices/StateContext"
77
import { SetupPageView } from "./SetupPageView"
88

99
export const SetupPage: FC = () => {
10-
const xServices = useContext(XServiceContext)
1110
const [authState, authSend] = useAuth()
1211
const [setupState, setupSend] = useMachine(setupMachine, {
1312
actions: {

0 commit comments

Comments
 (0)