diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 6039ea6c..f01288b0 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,4 +1,4 @@ -import { lazy, Suspense, useEffect, useState } from "react"; +import { lazy, Suspense } from "react"; import { Navigate, Route, HashRouter as Router, Routes } from "react-router-dom"; import "./App.scss"; @@ -18,9 +18,7 @@ import { List as ResourcesList } from "views/resource"; import { ApolloProvider } from "@apollo/client"; import { Spinner } from "components"; -import { useTranslation } from "react-i18next"; -import { UserState } from "types"; -import { UserContext } from "utils"; +import { UserProvider } from "utils"; import MessageSheet from "views/journal/MessageSheet"; import { Layout, LayoutMarginLess } from "views/Layout"; import { default as client } from "client"; @@ -29,42 +27,8 @@ import { Provider as FeatureFlagProvider } from "FeatureFlags"; const Map = lazy(() => import("views/map")); function App() { - const [userState, setUserState] = useState({ isLoggedin: false, email: "", username: "" }); - const { i18n } = useTranslation(); - - const setUserStateFromUserinfo = () => { - fetch("/oauth2/userinfo", { credentials: "include" }) - .then((response) => { - if (!response.ok) { - throw new Error("unauthenticated"); - } - return response.json(); - }) - .then((userInfo) => { - setUserState({ - isLoggedin: true, - email: userInfo.email, - username: userInfo.user || userInfo.preferredUsername, - }); - }) - .catch(() => { - setUserState({ isLoggedin: false, email: "", username: "" }); - }); - }; - - useEffect(() => { - setUserStateFromUserinfo(); - i18n.changeLanguage(); - - const interval = setInterval(() => { - setUserStateFromUserinfo(); - }, 10000); - - return () => clearInterval(interval); - }, [i18n]); - return ( - + @@ -189,7 +153,7 @@ function App() { - + ); } diff --git a/ui/src/FeatureFlags.tsx b/ui/src/FeatureFlags.tsx index b53169f9..971b54f6 100644 --- a/ui/src/FeatureFlags.tsx +++ b/ui/src/FeatureFlags.tsx @@ -23,7 +23,7 @@ const localFlagConfig = { const Provider = (props: PropsWithChildren) => { const { children } = props; - const userState = useContext(UserContext); + const { state: userState } = useContext(UserContext); useEffect(() => { const fliptProvider = new FliptWebProvider("sitrep-ui", { url: "https://flipt.sitrep.ch" }); @@ -38,11 +38,6 @@ const Provider = (props: PropsWithChildren) => { email: userState.email, }; OpenFeature.setContext(context); - - return () => { - console.log("closing openfeature provider"); - OpenFeature.close(); - }; }, [userState]); return {children}; diff --git a/ui/src/components/Navbar.tsx b/ui/src/components/Navbar.tsx index 16b061d6..49f24051 100644 --- a/ui/src/components/Navbar.tsx +++ b/ui/src/components/Navbar.tsx @@ -186,7 +186,7 @@ function VersionNavBar() { } function UserNavBar() { - const userState = useContext(UserContext); + const { state: userState } = useContext(UserContext); const { t } = useTranslation(); if (!userState.isLoggedin) return <>; diff --git a/ui/src/utils/UserContext.tsx b/ui/src/utils/UserContext.tsx index 8e2eca66..7f15a39b 100644 --- a/ui/src/utils/UserContext.tsx +++ b/ui/src/utils/UserContext.tsx @@ -1,5 +1,96 @@ -import { createContext } from "react"; +import { createContext, useReducer, useEffect, useCallback, useContext, ReactNode, Dispatch } from "react"; import { UserState } from "types"; -const UserContext = createContext({ isLoggedin: false, username: "", email: "" }); -export { UserContext }; +// Define the initial state +const initialState: UserState = { isLoggedin: false, username: "", email: "" }; + +// Define action types +type UserAction = { type: "LOGIN"; payload: { username: string; email: string } } | { type: "LOGOUT" }; + +// Define the reducer function +const userReducer = (state: UserState, action: UserAction): UserState => { + switch (action.type) { + case "LOGIN": + return { + isLoggedin: true, + username: action.payload.username, + email: action.payload.email, + }; + case "LOGOUT": + return { + isLoggedin: false, + username: "", + email: "", + }; + default: + return state; + } +}; + +// Create the UserContext with initial state and a dummy dispatch function +const UserContext = createContext<{ + state: UserState; + dispatch: Dispatch; +}>({ + state: initialState, + dispatch: () => null, +}); + +// Create the UserProvider component +const UserProvider = ({ children }: { children: ReactNode }) => { + const [state, dispatch] = useReducer(userReducer, initialState); + + return ( + + + {children} + + ); +}; + +const UserInfoFetcher = () => { + const { state: userState, dispatch } = useContext(UserContext); + const setUserStateFromUserinfo = useCallback(() => { + fetch("/oauth2/userinfo", { credentials: "include" }) + .then((response) => { + if (!response.ok) { + throw new Error("unauthenticated"); + } + return response.json(); + }) + .then((userInfo) => { + const newUserState = { + isLoggedin: true, + email: userInfo.email, + username: userInfo.user || userInfo.preferredUsername, + }; + + // Only update state if it has changed + if ( + newUserState.isLoggedin !== userState.isLoggedin || + newUserState.email !== userState.email || + newUserState.username !== userState.username + ) { + dispatch({ type: "LOGIN", payload: newUserState }); + } + }) + .catch(() => { + if (userState.isLoggedin) { + dispatch({ type: "LOGOUT" }); + } + }); + }, [userState, dispatch]); + + useEffect(() => { + setUserStateFromUserinfo(); + const interval = setInterval(() => { + setUserStateFromUserinfo(); + }, 30000); + + return () => clearInterval(interval); + }, [userState]); + + return <>; +}; + +export { UserContext, UserProvider }; diff --git a/ui/src/utils/index.tsx b/ui/src/utils/index.tsx index 81c1c399..0cbc69f4 100644 --- a/ui/src/utils/index.tsx +++ b/ui/src/utils/index.tsx @@ -1,3 +1,3 @@ -export { UserContext } from "./UserContext"; +export { UserContext, UserProvider } from "./UserContext"; export { ReloadPrompt } from "./ReloadSWPrompt"; diff --git a/ui/src/views/Layout.tsx b/ui/src/views/Layout.tsx index e40b5196..a720ea80 100644 --- a/ui/src/views/Layout.tsx +++ b/ui/src/views/Layout.tsx @@ -13,7 +13,7 @@ export interface LayoutProps { export const Layout = (props: LayoutProps) => { const [searchParams] = useSearchParams(); const { i18n } = useTranslation(); - const userState = useContext(UserContext); + const { state: userState } = useContext(UserContext); const lang = searchParams.get("lang"); useEffect(() => { @@ -40,7 +40,7 @@ export const Layout = (props: LayoutProps) => { export const LayoutMarginLess = (props: LayoutProps) => { const [searchParams] = useSearchParams(); const { i18n } = useTranslation(); - const userState = useContext(UserContext); + const { state: userState } = useContext(UserContext); const lang = searchParams.get("lang"); useEffect(() => { diff --git a/ui/src/views/map/Map.tsx b/ui/src/views/map/Map.tsx index dadda41c..e2c9d264 100644 --- a/ui/src/views/map/Map.tsx +++ b/ui/src/views/map/Map.tsx @@ -97,8 +97,7 @@ function Layers() { const { state } = useContext(LayerContext); return ( - - + <>
@@ -106,10 +105,11 @@ function Layers() { {/* Active Layer */} + {/* Inactive Layers */} l.id !== state.activeLayer) || []} /> - + ); } @@ -120,7 +120,7 @@ function LayerFetcher() { const { data, loading } = useQuery(GetLayers, { variables: { incidentId: incidentId || "" }, - pollInterval: 3000, + pollInterval: 2000, fetchPolicy: "cache-and-network", }); @@ -170,7 +170,6 @@ function ActiveLayer() { featureCollection={featureCollection} selectedFeature={state.selectedFeature} /> - ); } @@ -397,7 +396,10 @@ function InactiveLayer(props: { featureCollection: FeatureCollection; id: string function MapWithProvder() { return ( - + + + + ); } diff --git a/ui/src/views/map/controls/BabsIconController.tsx b/ui/src/views/map/controls/BabsIconController.tsx index 15a87150..2a55c09d 100644 --- a/ui/src/views/map/controls/BabsIconController.tsx +++ b/ui/src/views/map/controls/BabsIconController.tsx @@ -521,13 +521,12 @@ const FeatureDetailControlPanel = memo((props: BabsIconControllerProps) => { return (
Name
-
+
{ - e.preventDefault(); setEnteredText(e.target.value); }} value={enteredText}