import { ConfigProvider } from 'antd'; import getLocalStorageApi from 'api/browser/localstorage/get'; import setLocalStorageApi from 'api/browser/localstorage/set'; import NotFound from 'components/NotFound'; import Spinner from 'components/Spinner'; import { FeatureKeys } from 'constants/features'; import { LOCALSTORAGE } from 'constants/localStorage'; import ROUTES from 'constants/routes'; import AppLayout from 'container/AppLayout'; import useAnalytics from 'hooks/analytics/useAnalytics'; import { KeyboardHotkeysProvider } from 'hooks/hotkeys/useKeyboardHotkeys'; import { useThemeConfig } from 'hooks/useDarkMode'; import { LICENSE_PLAN_KEY } from 'hooks/useLicense'; import { NotificationProvider } from 'hooks/useNotifications'; import { ResourceProvider } from 'hooks/useResourceAttribute'; import history from 'lib/history'; import { identity, pickBy } from 'lodash-es'; import posthog from 'posthog-js'; import AlertRuleProvider from 'providers/Alert'; import { useAppContext } from 'providers/App/App'; import { IUser } from 'providers/App/types'; import { DashboardProvider } from 'providers/Dashboard/Dashboard'; import { QueryBuilderProvider } from 'providers/QueryBuilder'; import { Suspense, useCallback, useEffect, useState } from 'react'; import { Route, Router, Switch } from 'react-router-dom'; import { CompatRouter } from 'react-router-dom-v5-compat'; import { extractDomain, isCloudUser, isEECloudUser } from 'utils/app'; import PrivateRoute from './Private'; import defaultRoutes, { AppRoutes, LIST_LICENSES, SUPPORT_ROUTE, } from './routes'; function App(): JSX.Element { const themeConfig = useThemeConfig(); const { licenses, user, isFetchingUser, isFetchingLicenses, isFetchingFeatureFlags, userFetchError, licensesFetchError, featureFlagsFetchError, isLoggedIn: isLoggedInState, featureFlags, org, } = useAppContext(); const [routes, setRoutes] = useState(defaultRoutes); const { trackPageView } = useAnalytics(); const { hostname, pathname } = window.location; const isCloudUserVal = isCloudUser(); const enableAnalytics = useCallback( (user: IUser): void => { // wait for the required data to be loaded before doing init for anything! if (!isFetchingLicenses && licenses && org) { const orgName = org && Array.isArray(org) && org.length > 0 ? org[0].name : ''; const { name, email, role } = user; const identifyPayload = { email, name, company_name: orgName, role, source: 'signoz-ui', }; const sanitizedIdentifyPayload = pickBy(identifyPayload, identity); const domain = extractDomain(email); const hostNameParts = hostname.split('.'); const groupTraits = { name: orgName, tenant_id: hostNameParts[0], data_region: hostNameParts[1], tenant_url: hostname, company_domain: domain, source: 'signoz-ui', }; window.analytics.identify(email, sanitizedIdentifyPayload); window.analytics.group(domain, groupTraits); posthog?.identify(email, { email, name, orgName, tenant_id: hostNameParts[0], data_region: hostNameParts[1], tenant_url: hostname, company_domain: domain, source: 'signoz-ui', isPaidUser: !!licenses?.trialConvertedToSubscription, }); posthog?.group('company', domain, { name: orgName, tenant_id: hostNameParts[0], data_region: hostNameParts[1], tenant_url: hostname, company_domain: domain, source: 'signoz-ui', isPaidUser: !!licenses?.trialConvertedToSubscription, }); } }, [hostname, isFetchingLicenses, licenses, org], ); // eslint-disable-next-line sonarjs/cognitive-complexity useEffect(() => { if ( !isFetchingLicenses && licenses && !isFetchingUser && user && !!user.email ) { const isOnBasicPlan = licenses.licenses?.some( (license) => license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN, ) || licenses.licenses === null; const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER); if (isLoggedInState && user && user.id && user.email && !isIdentifiedUser) { setLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true'); } let updatedRoutes = defaultRoutes; // if the user is a cloud user if (isCloudUserVal || isEECloudUser()) { // if the user is on basic plan then remove billing if (isOnBasicPlan) { updatedRoutes = updatedRoutes.filter( (route) => route?.path !== ROUTES.BILLING, ); } // always add support route for cloud users updatedRoutes = [...updatedRoutes, SUPPORT_ROUTE]; } else { // if not a cloud user then remove billing and add list licenses route updatedRoutes = updatedRoutes.filter( (route) => route?.path !== ROUTES.BILLING, ); updatedRoutes = [...updatedRoutes, LIST_LICENSES]; } setRoutes(updatedRoutes); } }, [ isLoggedInState, user, licenses, isCloudUserVal, isFetchingLicenses, isFetchingUser, ]); useEffect(() => { if (pathname === ROUTES.ONBOARDING) { window.Intercom('update', { hide_default_launcher: true, }); } else { window.Intercom('update', { hide_default_launcher: false, }); } trackPageView(pathname); }, [pathname, trackPageView]); useEffect(() => { // feature flag shouldn't be loading and featureFlags or fetchError any one of this should be true indicating that req is complete // licenses should also be present. there is no check for licenses for loading and error as that is mandatory if not present then routing // to something went wrong which would ideally need a reload. if ( !isFetchingFeatureFlags && (featureFlags || featureFlagsFetchError) && licenses ) { let isChatSupportEnabled = false; let isPremiumSupportEnabled = false; if (featureFlags && featureFlags.length > 0) { isChatSupportEnabled = featureFlags.find((flag) => flag.name === FeatureKeys.CHAT_SUPPORT) ?.active || false; isPremiumSupportEnabled = featureFlags.find((flag) => flag.name === FeatureKeys.PREMIUM_SUPPORT) ?.active || false; } const showAddCreditCardModal = !isPremiumSupportEnabled && !licenses.trialConvertedToSubscription; if (isLoggedInState && isChatSupportEnabled && !showAddCreditCardModal) { window.Intercom('boot', { app_id: process.env.INTERCOM_APP_ID, email: user?.email || '', name: user?.name || '', }); } } }, [ isLoggedInState, user, pathname, licenses?.trialConvertedToSubscription, featureFlags, isFetchingFeatureFlags, featureFlagsFetchError, licenses, ]); useEffect(() => { if (!isFetchingUser && isCloudUserVal && user && user.email) { enableAnalytics(user); } }, [user, isFetchingUser, isCloudUserVal, enableAnalytics]); // if the user is in logged in state if (isLoggedInState) { if (pathname === ROUTES.HOME_PAGE) { history.replace(ROUTES.APPLICATION); } // if the setup calls are loading then return a spinner if (isFetchingLicenses || isFetchingUser || isFetchingFeatureFlags) { return ; } // if the required calls fails then return a something went wrong error // this needs to be on top of data missing error because if there is an error, data will never be loaded and it will // move to indefinitive loading if ( (userFetchError || licensesFetchError) && pathname !== ROUTES.SOMETHING_WENT_WRONG ) { history.replace(ROUTES.SOMETHING_WENT_WRONG); } // if all of the data is not set then return a spinner, this is required because there is some gap between loading states and data setting if ( (!licenses || !user.email || !featureFlags) && !userFetchError && !licensesFetchError ) { return ; } } return ( }> {routes.map(({ path, component, exact }) => ( ))} ); } export default App;