mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 04:39:01 +08:00
feat: handle onboarding visibility
This commit is contained in:
parent
a1090bfdc5
commit
845dc00568
@ -1,5 +1,6 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import getOrgUser from 'api/user/getOrgUser';
|
||||
import loginApi from 'api/user/login';
|
||||
import { Logout } from 'api/utils';
|
||||
import Spinner from 'components/Spinner';
|
||||
@ -8,8 +9,10 @@ import ROUTES from 'constants/routes';
|
||||
import useLicense from 'hooks/useLicense';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import { ReactChild, useEffect, useMemo } from 'react';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { ReactChild, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { matchPath, Redirect, useLocation } from 'react-router-dom';
|
||||
import { Dispatch } from 'redux';
|
||||
@ -17,6 +20,7 @@ import { AppState } from 'store/reducers';
|
||||
import { getInitialUserTokenRefreshToken } from 'store/utils';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_USER_IS_FETCH } from 'types/actions/app';
|
||||
import { Organization } from 'types/api/user/getOrganization';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { routePermission } from 'utils/permission';
|
||||
|
||||
@ -31,6 +35,14 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
const location = useLocation();
|
||||
const { pathname } = location;
|
||||
|
||||
const { org, orgPreferences } = useSelector<AppState, AppReducer>(
|
||||
(state) => state.app,
|
||||
);
|
||||
|
||||
const [isOnboardingComplete, setIsOnboardingComplete] = useState<
|
||||
boolean | null
|
||||
>(null);
|
||||
|
||||
const mapRoutes = useMemo(
|
||||
() =>
|
||||
new Map(
|
||||
@ -44,6 +56,18 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
[pathname],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (orgPreferences && !isEmpty(orgPreferences)) {
|
||||
const onboardingPreference = orgPreferences?.find(
|
||||
(preference: Record<string, any>) => preference.key === 'ORG_ONBOARDING',
|
||||
);
|
||||
|
||||
if (onboardingPreference) {
|
||||
setIsOnboardingComplete(onboardingPreference.value);
|
||||
}
|
||||
}
|
||||
}, [orgPreferences]);
|
||||
|
||||
const {
|
||||
data: licensesData,
|
||||
isFetching: isFetchingLicensesData,
|
||||
@ -53,9 +77,11 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
isUserFetching,
|
||||
isUserFetchingError,
|
||||
isLoggedIn: isLoggedInState,
|
||||
isFetchingOrgPreferences,
|
||||
} = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const { t } = useTranslation(['common']);
|
||||
|
||||
const localStorageUserAuthToken = getInitialUserTokenRefreshToken();
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
@ -66,6 +92,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
|
||||
const isOldRoute = oldRoutes.indexOf(pathname) > -1;
|
||||
|
||||
const [orgData, setOrgData] = useState<Organization | undefined>(undefined);
|
||||
|
||||
const isLocalStorageLoggedIn =
|
||||
getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true';
|
||||
|
||||
@ -81,6 +109,42 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
}
|
||||
};
|
||||
|
||||
const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({
|
||||
queryFn: () => {
|
||||
if (orgData && orgData.id !== undefined) {
|
||||
return getOrgUser({
|
||||
orgId: orgData.id,
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
queryKey: ['getOrgUser'],
|
||||
enabled: !isEmpty(orgData),
|
||||
});
|
||||
|
||||
const checkFirstTimeUser = (): boolean => {
|
||||
const users = orgUsers?.payload || [];
|
||||
|
||||
const remainingUsers = users.filter(
|
||||
(user) => user.email !== 'admin@signoz.cloud',
|
||||
);
|
||||
|
||||
return remainingUsers.length === 1;
|
||||
};
|
||||
|
||||
// Check if the onboarding should be shown based on the org users and onboarding completion status, wait for org users and preferences to load
|
||||
const shouldShowOnboarding = (): boolean => {
|
||||
// Only run this effect if the org users and preferences are loaded
|
||||
if (!isLoadingOrgUsers) {
|
||||
const isFirstUser = checkFirstTimeUser();
|
||||
|
||||
// Redirect to get started if it's not the first user or if the onboarding is complete
|
||||
return isFirstUser && !isOnboardingComplete;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleUserLoginIfTokenPresent = async (
|
||||
key: keyof typeof ROUTES,
|
||||
): Promise<void> => {
|
||||
@ -102,6 +166,16 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
response.payload.refreshJwt,
|
||||
);
|
||||
|
||||
const showOnboarding = shouldShowOnboarding();
|
||||
|
||||
if (
|
||||
userResponse &&
|
||||
showOnboarding &&
|
||||
userResponse.payload.role === 'ADMIN'
|
||||
) {
|
||||
history.push(ROUTES.ONBOARDING);
|
||||
}
|
||||
|
||||
if (
|
||||
userResponse &&
|
||||
route &&
|
||||
@ -160,6 +234,35 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
}
|
||||
}, [isFetchingLicensesData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (org && org.length > 0 && org[0].id !== undefined) {
|
||||
setOrgData(org[0]);
|
||||
}
|
||||
}, [org]);
|
||||
|
||||
const handleRouting = (): void => {
|
||||
const showOnboarding = shouldShowOnboarding();
|
||||
|
||||
if (showOnboarding) {
|
||||
history.push(ROUTES.ONBOARDING);
|
||||
} else {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Only run this effect if the org users and preferences are loaded
|
||||
if (!isLoadingOrgUsers && isOnboardingComplete !== null) {
|
||||
const isFirstUser = checkFirstTimeUser();
|
||||
|
||||
// Redirect to get started if it's not the first user or if the onboarding is complete
|
||||
if (isFirstUser && !isOnboardingComplete) {
|
||||
history.push(ROUTES.ONBOARDING);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isLoadingOrgUsers, isOnboardingComplete, orgUsers]);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
useEffect(() => {
|
||||
(async (): Promise<void> => {
|
||||
@ -183,7 +286,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
// no need to fetch the user and make user fetching false
|
||||
|
||||
if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
handleRouting();
|
||||
}
|
||||
dispatch({
|
||||
type: UPDATE_USER_IS_FETCH,
|
||||
@ -195,7 +298,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
} else if (pathname === ROUTES.HOME_PAGE) {
|
||||
// routing to application page over root page
|
||||
if (isLoggedInState) {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
handleRouting();
|
||||
} else {
|
||||
navigateToLoginIfNotLoggedIn();
|
||||
}
|
||||
@ -214,7 +317,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
return <Redirect to={ROUTES.SOMETHING_WENT_WRONG} />;
|
||||
}
|
||||
|
||||
if (isUserFetching) {
|
||||
if (isUserFetching || (isLoggedInState && isFetchingOrgPreferences)) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { ConfigProvider } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences';
|
||||
import NotFound from 'components/NotFound';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
@ -24,12 +25,17 @@ import AlertRuleProvider from 'providers/Alert';
|
||||
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { Suspense, useEffect, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_FEATURE_FLAG_RESPONSE } from 'types/actions/app';
|
||||
import {
|
||||
UPDATE_FEATURE_FLAG_RESPONSE,
|
||||
UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||
UPDATE_ORG_PREFERENCES,
|
||||
} from 'types/actions/app';
|
||||
import AppReducer, { User } from 'types/reducer/app';
|
||||
import { extractDomain, isCloudUser, isEECloudUser } from 'utils/app';
|
||||
|
||||
@ -65,6 +71,30 @@ function App(): JSX.Element {
|
||||
const isPremiumSupportEnabled =
|
||||
useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false;
|
||||
|
||||
const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({
|
||||
queryFn: () => getAllOrgPreferences(),
|
||||
queryKey: ['getOrgPreferences'],
|
||||
enabled: isLoggedInState,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (orgPreferences && !isLoadingOrgPreferences) {
|
||||
dispatch({
|
||||
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||
payload: {
|
||||
isFetchingOrgPreferences: false,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_ORG_PREFERENCES,
|
||||
payload: {
|
||||
orgPreferences: orgPreferences.payload?.data || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [orgPreferences, dispatch, isLoadingOrgPreferences]);
|
||||
|
||||
const featureResponse = useGetFeatureFlag((allFlags) => {
|
||||
dispatch({
|
||||
type: UPDATE_FEATURE_FLAG_RESPONSE,
|
||||
@ -182,6 +212,16 @@ function App(): JSX.Element {
|
||||
}, [isLoggedInState, isOnBasicPlan, user]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname === ROUTES.ONBOARDING) {
|
||||
window.Intercom('update', {
|
||||
hide_default_launcher: true,
|
||||
});
|
||||
} else {
|
||||
window.Intercom('update', {
|
||||
hide_default_launcher: false,
|
||||
});
|
||||
}
|
||||
|
||||
trackPageView(pathname);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pathname]);
|
||||
@ -204,6 +244,7 @@ function App(): JSX.Element {
|
||||
user,
|
||||
licenseData,
|
||||
isPremiumSupportEnabled,
|
||||
pathname,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -4,10 +4,15 @@ import '../OnboardingQuestionaire.styles.scss';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Input, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import editOrg from 'api/user/editOrg';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { ArrowRight, CheckCircle, Loader2 } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Dispatch, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_ORG_NAME } from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
export interface OrgData {
|
||||
@ -25,10 +30,9 @@ export interface OrgDetails {
|
||||
}
|
||||
|
||||
interface OrgQuestionsProps {
|
||||
isLoading: boolean;
|
||||
currentOrgData: OrgData | null;
|
||||
orgDetails: OrgDetails;
|
||||
setOrgDetails: (details: OrgDetails) => void;
|
||||
onNext: () => void;
|
||||
onNext: (details: OrgDetails) => void;
|
||||
}
|
||||
|
||||
const observabilityTools = {
|
||||
@ -49,12 +53,15 @@ const o11yFamiliarityOptions: Record<string, string> = {
|
||||
};
|
||||
|
||||
function OrgQuestions({
|
||||
isLoading,
|
||||
currentOrgData,
|
||||
orgDetails,
|
||||
setOrgDetails,
|
||||
onNext,
|
||||
}: OrgQuestionsProps): JSX.Element {
|
||||
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { notifications } = useNotifications();
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const { t } = useTranslation(['organizationsettings', 'common']);
|
||||
|
||||
const [organisationName, setOrganisationName] = useState<string>(
|
||||
orgDetails?.organisationName || '',
|
||||
@ -77,6 +84,78 @@ function OrgQuestions({
|
||||
setOrganisationName(orgDetails.organisationName);
|
||||
}, [orgDetails.organisationName]);
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const handleOrgNameUpdate = async (): Promise<void> => {
|
||||
/* Early bailout if orgData is not set or if the organisation name is not set or if the organisation name is empty or if the organisation name is the same as the one in the orgData */
|
||||
if (
|
||||
!currentOrgData ||
|
||||
!organisationName ||
|
||||
organisationName === '' ||
|
||||
orgDetails.organisationName === organisationName
|
||||
) {
|
||||
onNext({
|
||||
organisationName,
|
||||
usesObservability,
|
||||
observabilityTool,
|
||||
otherTool,
|
||||
familiarity,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const { statusCode, error } = await editOrg({
|
||||
isAnonymous: currentOrgData.isAnonymous,
|
||||
name: organisationName,
|
||||
orgId: currentOrgData.id,
|
||||
});
|
||||
if (statusCode === 200) {
|
||||
dispatch({
|
||||
type: UPDATE_ORG_NAME,
|
||||
payload: {
|
||||
orgId: currentOrgData?.id,
|
||||
name: orgDetails.organisationName,
|
||||
},
|
||||
});
|
||||
|
||||
logEvent('User Onboarding: Org Name Updated', {
|
||||
organisationName: orgDetails.organisationName,
|
||||
});
|
||||
|
||||
onNext({
|
||||
organisationName,
|
||||
usesObservability,
|
||||
observabilityTool,
|
||||
otherTool,
|
||||
familiarity,
|
||||
});
|
||||
} else {
|
||||
logEvent('User Onboarding: Org Name Update Failed', {
|
||||
organisationName: orgDetails.organisationName,
|
||||
});
|
||||
|
||||
notifications.error({
|
||||
message:
|
||||
error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isValidUsesObservability = (): boolean => {
|
||||
if (usesObservability === null) {
|
||||
return false;
|
||||
@ -112,23 +191,7 @@ function OrgQuestions({
|
||||
]);
|
||||
|
||||
const handleOnNext = (): void => {
|
||||
setOrgDetails({
|
||||
organisationName,
|
||||
usesObservability,
|
||||
observabilityTool,
|
||||
otherTool,
|
||||
familiarity,
|
||||
});
|
||||
|
||||
logEvent('User Onboarding: Org Questions Answered', {
|
||||
organisationName,
|
||||
usesObservability,
|
||||
observabilityTool,
|
||||
otherTool,
|
||||
familiarity,
|
||||
});
|
||||
|
||||
onNext();
|
||||
handleOrgNameUpdate();
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -4,9 +4,9 @@ import { Skeleton } from 'antd';
|
||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import updateProfileAPI from 'api/onboarding/updateProfile';
|
||||
import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences';
|
||||
import getOrgPreference from 'api/preferences/getOrgPreference';
|
||||
import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference';
|
||||
import editOrg from 'api/user/editOrg';
|
||||
import getOrgUser from 'api/user/getOrgUser';
|
||||
import { AxiosError } from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
@ -16,12 +16,14 @@ import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { Dispatch, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_ORG_NAME } from 'types/actions/app';
|
||||
import {
|
||||
UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||
UPDATE_ORG_PREFERENCES,
|
||||
} from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import {
|
||||
@ -67,8 +69,8 @@ const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = {
|
||||
|
||||
function OnboardingQuestionaire(): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const [currentStep, setCurrentStep] = useState<number>(4);
|
||||
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const [currentStep, setCurrentStep] = useState<number>(1);
|
||||
const [orgDetails, setOrgDetails] = useState<OrgDetails>(INITIAL_ORG_DETAILS);
|
||||
const [signozDetails, setSignozDetails] = useState<SignozDetails>(
|
||||
INITIAL_SIGNOZ_DETAILS,
|
||||
@ -81,9 +83,6 @@ function OnboardingQuestionaire(): JSX.Element {
|
||||
InviteTeamMembersProps[] | null
|
||||
>(null);
|
||||
|
||||
const { t } = useTranslation(['organizationsettings', 'common']);
|
||||
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({
|
||||
queryFn: () =>
|
||||
getOrgUser({
|
||||
@ -93,26 +92,57 @@ function OnboardingQuestionaire(): JSX.Element {
|
||||
});
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
const [orgData, setOrgData] = useState<OrgData | null>(null);
|
||||
const [currentOrgData, setCurrentOrgData] = useState<OrgData | null>(null);
|
||||
const [isOnboardingComplete, setIsOnboardingComplete] = useState<boolean>(
|
||||
false,
|
||||
);
|
||||
|
||||
const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({
|
||||
const {
|
||||
data: onboardingPreferenceData,
|
||||
isLoading: isLoadingOnboardingPreference,
|
||||
} = useQuery({
|
||||
queryFn: () => getOrgPreference({ preferenceID: 'ORG_ONBOARDING' }),
|
||||
queryKey: ['getOrgPreferences', 'ORG_ONBOARDING'],
|
||||
});
|
||||
|
||||
const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({
|
||||
queryFn: () => getAllOrgPreferences(),
|
||||
queryKey: ['getOrgPreferences'],
|
||||
enabled: isOnboardingComplete,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoadingOrgPreferences && !isEmpty(orgPreferences?.payload?.data)) {
|
||||
const preferenceId = orgPreferences?.payload?.data?.preference_id;
|
||||
const preferenceValue = orgPreferences?.payload?.data?.preference_value;
|
||||
if (orgPreferences && !isLoadingOrgPreferences) {
|
||||
dispatch({
|
||||
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||
payload: {
|
||||
isFetchingOrgPreferences: false,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_ORG_PREFERENCES,
|
||||
payload: {
|
||||
orgPreferences: orgPreferences.payload?.data || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [orgPreferences, dispatch, isLoadingOrgPreferences]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isLoadingOnboardingPreference &&
|
||||
!isEmpty(onboardingPreferenceData?.payload?.data)
|
||||
) {
|
||||
const preferenceId = onboardingPreferenceData?.payload?.data?.preference_id;
|
||||
const preferenceValue =
|
||||
onboardingPreferenceData?.payload?.data?.preference_value;
|
||||
|
||||
if (preferenceId === 'ORG_ONBOARDING') {
|
||||
setIsOnboardingComplete(preferenceValue as boolean);
|
||||
}
|
||||
}
|
||||
}, [orgPreferences, isLoadingOrgPreferences]);
|
||||
}, [onboardingPreferenceData, isLoadingOnboardingPreference]);
|
||||
|
||||
const checkFirstTimeUser = (): boolean => {
|
||||
const users = orgUsers?.payload || [];
|
||||
@ -126,7 +156,7 @@ function OnboardingQuestionaire(): JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
// Only run this effect if the org users and preferences are loaded
|
||||
if (!isLoadingOrgUsers && !isLoadingOrgPreferences) {
|
||||
if (!isLoadingOrgUsers && !isLoadingOnboardingPreference) {
|
||||
const isFirstUser = checkFirstTimeUser();
|
||||
|
||||
// Redirect to get started if it's not the first user or if the onboarding is complete
|
||||
@ -144,14 +174,14 @@ function OnboardingQuestionaire(): JSX.Element {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
isLoadingOrgUsers,
|
||||
isLoadingOrgPreferences,
|
||||
isLoadingOnboardingPreference,
|
||||
isOnboardingComplete,
|
||||
orgUsers,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (org) {
|
||||
setOrgData(org[0]);
|
||||
setCurrentOrgData(org[0]);
|
||||
|
||||
setOrgDetails({
|
||||
...orgDetails,
|
||||
@ -166,70 +196,6 @@ function OnboardingQuestionaire(): JSX.Element {
|
||||
optimiseSignozDetails.hostsPerDay === 0 &&
|
||||
optimiseSignozDetails.services === 0;
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const handleOrgNameUpdate = async (): Promise<void> => {
|
||||
/* Early bailout if orgData is not set or if the organisation name is not set or if the organisation name is empty or if the organisation name is the same as the one in the orgData */
|
||||
if (
|
||||
!orgData ||
|
||||
!orgDetails.organisationName ||
|
||||
orgDetails.organisationName === '' ||
|
||||
orgData.name === orgDetails.organisationName
|
||||
) {
|
||||
setCurrentStep(2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const { statusCode, error } = await editOrg({
|
||||
isAnonymous: orgData?.isAnonymous,
|
||||
name: orgDetails.organisationName,
|
||||
orgId: orgData?.id,
|
||||
});
|
||||
if (statusCode === 200) {
|
||||
dispatch({
|
||||
type: UPDATE_ORG_NAME,
|
||||
payload: {
|
||||
orgId: orgData?.id,
|
||||
name: orgDetails.organisationName,
|
||||
},
|
||||
});
|
||||
|
||||
logEvent('User Onboarding: Org Name Updated', {
|
||||
organisationName: orgDetails.organisationName,
|
||||
});
|
||||
|
||||
setCurrentStep(2);
|
||||
} else {
|
||||
logEvent('User Onboarding: Org Name Update Failed', {
|
||||
organisationName: orgDetails.organisationName,
|
||||
});
|
||||
|
||||
notifications.error({
|
||||
message:
|
||||
error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleOrgDetailsUpdate = (): void => {
|
||||
handleOrgNameUpdate();
|
||||
};
|
||||
|
||||
const { mutate: updateProfile, isLoading: isUpdatingProfile } = useMutation(
|
||||
updateProfileAPI,
|
||||
{
|
||||
@ -289,20 +255,22 @@ function OnboardingQuestionaire(): JSX.Element {
|
||||
</div>
|
||||
|
||||
<div className="onboarding-questionaire-content">
|
||||
{(isLoadingOrgPreferences || isLoadingOrgUsers) && (
|
||||
{(isLoadingOnboardingPreference || isLoadingOrgUsers) && (
|
||||
<div className="onboarding-questionaire-loading-container">
|
||||
<Skeleton />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoadingOrgPreferences && !isLoadingOrgUsers && (
|
||||
{!isLoadingOnboardingPreference && !isLoadingOrgUsers && (
|
||||
<>
|
||||
{currentStep === 1 && (
|
||||
<OrgQuestions
|
||||
isLoading={isLoading}
|
||||
currentOrgData={currentOrgData}
|
||||
orgDetails={orgDetails}
|
||||
setOrgDetails={setOrgDetails}
|
||||
onNext={handleOrgDetailsUpdate}
|
||||
onNext={(orgDetails: OrgDetails): void => {
|
||||
setOrgDetails(orgDetails);
|
||||
setCurrentStep(2);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@ -8,10 +8,12 @@ import {
|
||||
UPDATE_CURRENT_ERROR,
|
||||
UPDATE_CURRENT_VERSION,
|
||||
UPDATE_FEATURE_FLAG_RESPONSE,
|
||||
UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||
UPDATE_LATEST_VERSION,
|
||||
UPDATE_LATEST_VERSION_ERROR,
|
||||
UPDATE_ORG,
|
||||
UPDATE_ORG_NAME,
|
||||
UPDATE_ORG_PREFERENCES,
|
||||
UPDATE_USER,
|
||||
UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN,
|
||||
UPDATE_USER_FLAG,
|
||||
@ -59,6 +61,8 @@ const InitialValue: InitialValueTypes = {
|
||||
userFlags: {},
|
||||
ee: 'Y',
|
||||
setupCompleted: true,
|
||||
orgPreferences: null,
|
||||
isFetchingOrgPreferences: true,
|
||||
};
|
||||
|
||||
const appReducer = (
|
||||
@ -73,6 +77,17 @@ const appReducer = (
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_ORG_PREFERENCES: {
|
||||
return { ...state, orgPreferences: action.payload.orgPreferences };
|
||||
}
|
||||
|
||||
case UPDATE_IS_FETCHING_ORG_PREFERENCES: {
|
||||
return {
|
||||
...state,
|
||||
isFetchingOrgPreferences: action.payload.isFetchingOrgPreferences,
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_FEATURE_FLAG_RESPONSE: {
|
||||
return {
|
||||
...state,
|
||||
|
@ -276,26 +276,39 @@ notifications - 2050
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
src: url('../public/fonts/Inter-VariableFont_opsz,wght.ttf') format('truetype');
|
||||
font-weight: 300 700;
|
||||
font-style: normal;
|
||||
font-family: 'Inter';
|
||||
src: url('../public/fonts/Inter-VariableFont_opsz,wght.ttf') format('truetype');
|
||||
font-weight: 300 700;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Work Sans';
|
||||
src: url('../public/fonts/WorkSans-VariableFont_wght.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-family: 'Work Sans';
|
||||
src: url('../public/fonts/WorkSans-VariableFont_wght.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Space Mono';
|
||||
src: url('../public/fonts/SpaceMono-Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-family: 'Space Mono';
|
||||
src: url('../public/fonts/SpaceMono-Regular.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Fira Code';
|
||||
src: url('../public/fonts/FiraCode-VariableFont_wght.ttf') format('truetype');
|
||||
font-weight: 300 700;
|
||||
font-style: normal;
|
||||
font-family: 'Fira Code';
|
||||
src: url('../public/fonts/FiraCode-VariableFont_wght.ttf') format('truetype');
|
||||
font-weight: 300 700;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
@ -25,7 +25,10 @@ export const UPDATE_ORG_NAME = 'UPDATE_ORG_NAME';
|
||||
export const UPDATE_ORG = 'UPDATE_ORG';
|
||||
export const UPDATE_CONFIGS = 'UPDATE_CONFIGS';
|
||||
export const UPDATE_USER_FLAG = 'UPDATE_USER_FLAG';
|
||||
export const UPDATE_ORG_PREFERENCES = 'UPDATE_ORG_PREFERENCES';
|
||||
export const UPDATE_FEATURE_FLAG_RESPONSE = 'UPDATE_FEATURE_FLAG_RESPONSE';
|
||||
export const UPDATE_IS_FETCHING_ORG_PREFERENCES =
|
||||
'UPDATE_IS_FETCHING_ORG_PREFERENCES';
|
||||
|
||||
export interface LoggedInUser {
|
||||
type: typeof LOGGED_IN;
|
||||
@ -130,6 +133,20 @@ export interface UpdateFeatureFlag {
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateOrgPreferences {
|
||||
type: typeof UPDATE_ORG_PREFERENCES;
|
||||
payload: {
|
||||
orgPreferences: AppReducer['orgPreferences'];
|
||||
};
|
||||
}
|
||||
|
||||
export interface UpdateIsFetchingOrgPreferences {
|
||||
type: typeof UPDATE_IS_FETCHING_ORG_PREFERENCES;
|
||||
payload: {
|
||||
isFetchingOrgPreferences: AppReducer['isFetchingOrgPreferences'];
|
||||
};
|
||||
}
|
||||
|
||||
export type AppAction =
|
||||
| LoggedInUser
|
||||
| UpdateAppVersion
|
||||
@ -143,4 +160,6 @@ export type AppAction =
|
||||
| UpdateOrg
|
||||
| UpdateConfigs
|
||||
| UpdateUserFlag
|
||||
| UpdateFeatureFlag;
|
||||
| UpdateFeatureFlag
|
||||
| UpdateOrgPreferences
|
||||
| UpdateIsFetchingOrgPreferences;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { OrgPreference } from 'types/reducer/app';
|
||||
|
||||
export interface GetOrgPreferenceResponseProps {
|
||||
status: string;
|
||||
data: Record<string, unknown>;
|
||||
@ -10,7 +12,7 @@ export interface GetUserPreferenceResponseProps {
|
||||
|
||||
export interface GetAllOrgPreferencesResponseProps {
|
||||
status: string;
|
||||
data: Record<string, unknown>;
|
||||
data: OrgPreference[];
|
||||
}
|
||||
|
||||
export interface GetAllUserPreferencesResponseProps {
|
||||
|
@ -15,6 +15,18 @@ export interface User {
|
||||
profilePictureURL: UserPayload['profilePictureURL'];
|
||||
}
|
||||
|
||||
export interface OrgPreference {
|
||||
key: string;
|
||||
name: string;
|
||||
description: string;
|
||||
valueType: string;
|
||||
defaultValue: boolean;
|
||||
allowedValues: any[];
|
||||
isDiscreteValues: boolean;
|
||||
allowedScopes: string[];
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
export default interface AppReducer {
|
||||
isLoggedIn: boolean;
|
||||
currentVersion: string;
|
||||
@ -30,6 +42,8 @@ export default interface AppReducer {
|
||||
userFlags: null | UserFlags;
|
||||
ee: 'Y' | 'N';
|
||||
setupCompleted: boolean;
|
||||
orgPreferences: OrgPreference[] | null;
|
||||
isFetchingOrgPreferences: boolean;
|
||||
featureResponse: {
|
||||
data: FeatureFlagPayload[] | null;
|
||||
refetch: QueryObserverBaseResult['refetch'];
|
||||
|
@ -86,7 +86,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
LOGS_PIPELINES: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
TRACE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
GET_STARTED: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
ONBOARDING: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
ONBOARDING: ['ADMIN'],
|
||||
GET_STARTED_APPLICATION_MONITORING: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
GET_STARTED_INFRASTRUCTURE_MONITORING: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
GET_STARTED_LOGS_MANAGEMENT: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
|
Loading…
x
Reference in New Issue
Block a user