Files
rowy/src/contexts/AppContext.tsx
2021-09-17 16:03:15 +10:00

185 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useState, useContext, useMemo } from "react";
import firebase from "firebase/app";
import createPersistedState from "use-persisted-state";
import _merge from "lodash/merge";
import Helmet from "react-helmet";
import { useMediaQuery, ThemeProvider, CssBaseline } from "@mui/material";
import ErrorBoundary from "components/ErrorBoundary";
import { projectId, auth, db } from "@src/firebase";
import useDoc from "hooks/useDoc";
import { name } from "@root/package.json";
import { PUBLIC_SETTINGS, USERS } from "config/dbPaths";
import { analytics } from "analytics";
import themes from "theme";
const useThemeState = createPersistedState("__ROWY__THEME");
const useThemeOverriddenState = createPersistedState(
"__ROWY__THEME_OVERRIDDEN"
);
interface IAppContext {
projectId: string;
currentUser: firebase.User | null | undefined;
userClaims: Record<string, any> | undefined;
userRoles: null | string[];
authToken: string;
userDoc: any;
theme: keyof typeof themes;
themeOverridden: boolean;
setTheme: React.Dispatch<React.SetStateAction<keyof typeof themes>>;
setThemeOverridden: React.Dispatch<React.SetStateAction<boolean>>;
}
export const AppContext = React.createContext<IAppContext>({
projectId: "",
currentUser: undefined,
userClaims: undefined,
userRoles: [],
authToken: "",
userDoc: undefined,
theme: "light",
themeOverridden: false,
setTheme: () => {},
setThemeOverridden: () => {},
});
export const useAppContext = () => useContext(AppContext);
export const AppProvider: React.FC = ({ children }) => {
// Store currentUser from Firebase Auth
const [currentUser, setCurrentUser] = useState<
firebase.User | null | undefined
>();
// Store user auth data
const [userClaims, setUserClaims] =
useState<IAppContext["userClaims"]>(undefined);
const [userRoles, setUserRoles] = useState<IAppContext["userRoles"]>([]);
const [authToken, setAuthToken] = useState<IAppContext["authToken"]>("");
// Get user data from Firebase Auth event
useEffect(() => {
auth.onAuthStateChanged((auth) => {
setCurrentUser(auth);
if (auth)
auth.getIdTokenResult(true).then((results) => {
setAuthToken(results.token);
setUserRoles(
Array.isArray(results.claims.roles) ? results.claims.roles : []
);
setUserClaims(results.claims);
});
});
}, []);
useEffect(() => {
document.title = `${projectId}${name}`;
}, []);
const [publicSettings] = useDoc(
{ path: PUBLIC_SETTINGS },
{ createIfMissing: true }
);
// Store matching userDoc
const [userDoc, dispatchUserDoc] = useDoc({}, { createIfMissing: true });
// Get userDoc
useEffect(() => {
if (currentUser) {
analytics.setUserId(currentUser.uid);
analytics.setUserProperties({ instance: window.location.hostname });
dispatchUserDoc({ path: `${USERS}/${currentUser.uid}` });
}
}, [currentUser]);
// Set userDoc if it doesnt exist
useEffect(() => {
if (
(!userDoc.doc || !userDoc.doc.user) &&
!userDoc.loading &&
userDoc.path &&
currentUser
) {
const userFields = ["email", "displayName", "photoURL", "phoneNumber"];
const user = userFields.reduce((acc, curr) => {
if (currentUser[curr]) return { ...acc, [curr]: currentUser[curr] };
return acc;
}, {});
console.log("create user", userDoc.path, user);
db.doc(userDoc.path).set({ user }, { merge: true });
}
}, [userDoc, currentUser]);
// Infer theme based on system settings
const prefersDarkTheme = useMediaQuery("(prefers-color-scheme: dark)", {
noSsr: true,
});
// Store theme
const [theme, setTheme] = useThemeState<keyof typeof themes>(
prefersDarkTheme ? "dark" : "light"
);
// Store if theme was overridden
const [themeOverridden, setThemeOverridden] = useThemeOverriddenState(false);
// Update theme when system settings change
useEffect(() => {
if (themeOverridden) return;
if (prefersDarkTheme && theme !== "dark") setTheme("dark");
if (!prefersDarkTheme && theme !== "light") setTheme("light");
}, [prefersDarkTheme, themeOverridden]);
// Customize theme from project public settings & user settings
const customizedThemes = useMemo(() => {
const lightCustomizations = _merge(
{},
publicSettings.doc?.theme?.base,
publicSettings.doc?.theme?.light,
userDoc.doc?.theme?.base,
userDoc.doc?.theme?.light
);
const darkCustomizations = _merge(
{},
publicSettings.doc?.theme?.base,
publicSettings.doc?.theme?.dark,
userDoc.doc?.theme?.base,
userDoc.doc?.theme?.dark
);
return {
light: themes.light(lightCustomizations),
dark: themes.dark(darkCustomizations),
};
}, [userDoc.doc, publicSettings.doc]);
return (
<AppContext.Provider
value={{
projectId,
currentUser,
userClaims,
userRoles,
authToken,
userDoc: { state: userDoc, dispatch: dispatchUserDoc },
theme,
themeOverridden,
setTheme,
setThemeOverridden,
}}
>
{Array.isArray(customizedThemes[theme].typography.fontCssUrls) && (
<Helmet>
{customizedThemes[theme].typography.fontCssUrls!.map((url) => (
<link key={url} rel="stylesheet" href={url} />
))}
</Helmet>
)}
<ThemeProvider theme={customizedThemes[theme]}>
<CssBaseline />
<ErrorBoundary>{children}</ErrorBoundary>
</ThemeProvider>
</AppContext.Provider>
);
};