mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-16 15:35:55 +08:00
feat: improve async handling for org onboarding cases (#6342)
This commit is contained in:
parent
4978fb9599
commit
c7d0598ec0
@ -9,7 +9,7 @@ import ROUTES from 'constants/routes';
|
|||||||
import useLicense from 'hooks/useLicense';
|
import useLicense from 'hooks/useLicense';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { isEmpty } from 'lodash-es';
|
import { isEmpty, isNull } from 'lodash-es';
|
||||||
import { ReactChild, useEffect, useMemo, useState } from 'react';
|
import { ReactChild, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
@ -35,13 +35,18 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { pathname } = location;
|
const { pathname } = location;
|
||||||
|
|
||||||
const { org, orgPreferences } = useSelector<AppState, AppReducer>(
|
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||||
(state) => state.app,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isOnboardingComplete, setIsOnboardingComplete] = useState<
|
const {
|
||||||
boolean | null
|
org,
|
||||||
>(null);
|
orgPreferences,
|
||||||
|
user,
|
||||||
|
role,
|
||||||
|
isUserFetching,
|
||||||
|
isUserFetchingError,
|
||||||
|
isLoggedIn: isLoggedInState,
|
||||||
|
isFetchingOrgPreferences,
|
||||||
|
} = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
const mapRoutes = useMemo(
|
const mapRoutes = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -56,30 +61,19 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
[pathname],
|
[pathname],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const isOnboardingComplete = useMemo(
|
||||||
if (orgPreferences && !isEmpty(orgPreferences)) {
|
() =>
|
||||||
const onboardingPreference = orgPreferences?.find(
|
orgPreferences?.find(
|
||||||
(preference: Record<string, any>) => preference.key === 'ORG_ONBOARDING',
|
(preference: Record<string, any>) => preference.key === 'ORG_ONBOARDING',
|
||||||
);
|
)?.value,
|
||||||
|
[orgPreferences],
|
||||||
if (onboardingPreference) {
|
);
|
||||||
setIsOnboardingComplete(onboardingPreference.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [orgPreferences]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: licensesData,
|
data: licensesData,
|
||||||
isFetching: isFetchingLicensesData,
|
isFetching: isFetchingLicensesData,
|
||||||
} = useLicense();
|
} = useLicense();
|
||||||
|
|
||||||
const {
|
|
||||||
isUserFetching,
|
|
||||||
isUserFetchingError,
|
|
||||||
isLoggedIn: isLoggedInState,
|
|
||||||
isFetchingOrgPreferences,
|
|
||||||
} = useSelector<AppState, AppReducer>((state) => state.app);
|
|
||||||
|
|
||||||
const { t } = useTranslation(['common']);
|
const { t } = useTranslation(['common']);
|
||||||
|
|
||||||
const localStorageUserAuthToken = getInitialUserTokenRefreshToken();
|
const localStorageUserAuthToken = getInitialUserTokenRefreshToken();
|
||||||
@ -135,7 +129,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
// Check if the onboarding should be shown based on the org users and onboarding completion status, wait for org users and preferences to load
|
// 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 => {
|
const shouldShowOnboarding = (): boolean => {
|
||||||
// Only run this effect if the org users and preferences are loaded
|
// Only run this effect if the org users and preferences are loaded
|
||||||
if (!isLoadingOrgUsers) {
|
|
||||||
|
if (!isLoadingOrgUsers && !isFetchingOrgPreferences) {
|
||||||
const isFirstUser = checkFirstTimeUser();
|
const isFirstUser = checkFirstTimeUser();
|
||||||
|
|
||||||
// Redirect to get started if it's not the first user or if the onboarding is complete
|
// Redirect to get started if it's not the first user or if the onboarding is complete
|
||||||
@ -145,6 +140,26 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRedirectForOrgOnboarding = (key: string): void => {
|
||||||
|
if (
|
||||||
|
isLoggedInState &&
|
||||||
|
!isFetchingOrgPreferences &&
|
||||||
|
!isLoadingOrgUsers &&
|
||||||
|
!isEmpty(orgUsers?.payload) &&
|
||||||
|
!isNull(orgPreferences)
|
||||||
|
) {
|
||||||
|
if (key === 'ONBOARDING' && isOnboardingComplete) {
|
||||||
|
history.push(ROUTES.APPLICATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFirstTimeUser = checkFirstTimeUser();
|
||||||
|
|
||||||
|
if (isFirstTimeUser && !isOnboardingComplete) {
|
||||||
|
history.push(ROUTES.ONBOARDING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleUserLoginIfTokenPresent = async (
|
const handleUserLoginIfTokenPresent = async (
|
||||||
key: keyof typeof ROUTES,
|
key: keyof typeof ROUTES,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
@ -166,15 +181,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
response.payload.refreshJwt,
|
response.payload.refreshJwt,
|
||||||
);
|
);
|
||||||
|
|
||||||
const showOnboarding = shouldShowOnboarding();
|
handleRedirectForOrgOnboarding(key);
|
||||||
|
|
||||||
if (
|
|
||||||
userResponse &&
|
|
||||||
showOnboarding &&
|
|
||||||
userResponse.payload.role === 'ADMIN'
|
|
||||||
) {
|
|
||||||
history.push(ROUTES.ONBOARDING);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
userResponse &&
|
userResponse &&
|
||||||
@ -203,7 +210,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
) {
|
) {
|
||||||
handleUserLoginIfTokenPresent(key);
|
handleUserLoginIfTokenPresent(key);
|
||||||
} else {
|
} else {
|
||||||
// user does have localstorage values
|
handleRedirectForOrgOnboarding(key);
|
||||||
|
|
||||||
navigateToLoginIfNotLoggedIn(isLocalStorageLoggedIn);
|
navigateToLoginIfNotLoggedIn(isLocalStorageLoggedIn);
|
||||||
}
|
}
|
||||||
@ -241,9 +248,9 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
}, [org]);
|
}, [org]);
|
||||||
|
|
||||||
const handleRouting = (): void => {
|
const handleRouting = (): void => {
|
||||||
const showOnboarding = shouldShowOnboarding();
|
const showOrgOnboarding = shouldShowOnboarding();
|
||||||
|
|
||||||
if (showOnboarding) {
|
if (showOrgOnboarding && !isOnboardingComplete) {
|
||||||
history.push(ROUTES.ONBOARDING);
|
history.push(ROUTES.ONBOARDING);
|
||||||
} else {
|
} else {
|
||||||
history.push(ROUTES.APPLICATION);
|
history.push(ROUTES.APPLICATION);
|
||||||
@ -251,17 +258,27 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only run this effect if the org users and preferences are loaded
|
const { isPrivate } = currentRoute || {
|
||||||
if (!isLoadingOrgUsers && isOnboardingComplete !== null) {
|
isPrivate: false,
|
||||||
const isFirstUser = checkFirstTimeUser();
|
};
|
||||||
|
|
||||||
// Redirect to get started if it's not the first user or if the onboarding is complete
|
if (isLoggedInState && role && role !== 'ADMIN') {
|
||||||
if (isFirstUser && !isOnboardingComplete) {
|
setIsLoading(false);
|
||||||
history.push(ROUTES.ONBOARDING);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [isLoadingOrgUsers, isOnboardingComplete, orgUsers]);
|
if (!isPrivate) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isEmpty(user) &&
|
||||||
|
!isFetchingOrgPreferences &&
|
||||||
|
!isEmpty(orgUsers?.payload) &&
|
||||||
|
!isNull(orgPreferences)
|
||||||
|
) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [currentRoute, user, role, orgUsers, orgPreferences]);
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -284,7 +301,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
handlePrivateRoutes(key);
|
handlePrivateRoutes(key);
|
||||||
} else {
|
} else {
|
||||||
// no need to fetch the user and make user fetching false
|
// no need to fetch the user and make user fetching false
|
||||||
|
|
||||||
if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') {
|
if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') {
|
||||||
handleRouting();
|
handleRouting();
|
||||||
}
|
}
|
||||||
@ -311,13 +327,20 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
history.push(ROUTES.SOMETHING_WENT_WRONG);
|
history.push(ROUTES.SOMETHING_WENT_WRONG);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, [dispatch, isLoggedInState, currentRoute, licensesData]);
|
}, [
|
||||||
|
dispatch,
|
||||||
|
isLoggedInState,
|
||||||
|
currentRoute,
|
||||||
|
licensesData,
|
||||||
|
orgUsers,
|
||||||
|
orgPreferences,
|
||||||
|
]);
|
||||||
|
|
||||||
if (isUserFetchingError) {
|
if (isUserFetchingError) {
|
||||||
return <Redirect to={ROUTES.SOMETHING_WENT_WRONG} />;
|
return <Redirect to={ROUTES.SOMETHING_WENT_WRONG} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUserFetching || (isLoggedInState && isFetchingOrgPreferences)) {
|
if (isUserFetching || isLoading) {
|
||||||
return <Spinner tip="Loading..." />;
|
return <Spinner tip="Loading..." />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,7 +312,7 @@ export default function Onboarding(): JSX.Element {
|
|||||||
<div
|
<div
|
||||||
onClick={(): void => {
|
onClick={(): void => {
|
||||||
logEvent('Onboarding V2: Skip Button Clicked', {});
|
logEvent('Onboarding V2: Skip Button Clicked', {});
|
||||||
history.push('/');
|
history.push(ROUTES.APPLICATION);
|
||||||
}}
|
}}
|
||||||
className="skip-to-console"
|
className="skip-to-console"
|
||||||
>
|
>
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
|
Loader2,
|
||||||
Plus,
|
Plus,
|
||||||
TriangleAlert,
|
TriangleAlert,
|
||||||
X,
|
X,
|
||||||
@ -33,6 +34,7 @@ interface TeamMember {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface InviteTeamMembersProps {
|
interface InviteTeamMembersProps {
|
||||||
|
isLoading: boolean;
|
||||||
teamMembers: TeamMember[] | null;
|
teamMembers: TeamMember[] | null;
|
||||||
setTeamMembers: (teamMembers: TeamMember[]) => void;
|
setTeamMembers: (teamMembers: TeamMember[]) => void;
|
||||||
onNext: () => void;
|
onNext: () => void;
|
||||||
@ -40,6 +42,7 @@ interface InviteTeamMembersProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function InviteTeamMembers({
|
function InviteTeamMembers({
|
||||||
|
isLoading,
|
||||||
teamMembers,
|
teamMembers,
|
||||||
setTeamMembers,
|
setTeamMembers,
|
||||||
onNext,
|
onNext,
|
||||||
@ -67,8 +70,6 @@ function InviteTeamMembers({
|
|||||||
|
|
||||||
const [disableNextButton, setDisableNextButton] = useState<boolean>(false);
|
const [disableNextButton, setDisableNextButton] = useState<boolean>(false);
|
||||||
|
|
||||||
const [allInvitesSent, setAllInvitesSent] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const defaultTeamMember: TeamMember = {
|
const defaultTeamMember: TeamMember = {
|
||||||
email: '',
|
email: '',
|
||||||
role: 'EDITOR',
|
role: 'EDITOR',
|
||||||
@ -157,7 +158,6 @@ function InviteTeamMembers({
|
|||||||
setError(null);
|
setError(null);
|
||||||
setHasErrors(false);
|
setHasErrors(false);
|
||||||
setInviteUsersErrorResponse(null);
|
setInviteUsersErrorResponse(null);
|
||||||
setAllInvitesSent(true);
|
|
||||||
|
|
||||||
setInviteUsersSuccessResponse(successfulInvites);
|
setInviteUsersSuccessResponse(successfulInvites);
|
||||||
|
|
||||||
@ -358,33 +358,36 @@ function InviteTeamMembers({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{inviteUsersSuccessResponse && (
|
|
||||||
<div className="success-message-container invite-users-success-message-container">
|
|
||||||
{inviteUsersSuccessResponse?.map((success, index) => (
|
|
||||||
<Typography.Text
|
|
||||||
className="success-message"
|
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
|
||||||
key={`${success}-${index}`}
|
|
||||||
>
|
|
||||||
<CheckCircle size={14} /> {success}
|
|
||||||
</Typography.Text>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasErrors && (
|
{hasErrors && (
|
||||||
<div className="error-message-container invite-users-error-message-container">
|
<>
|
||||||
{inviteUsersErrorResponse?.map((error, index) => (
|
{/* show only when invites are sent successfully & partial error is present */}
|
||||||
<Typography.Text
|
{inviteUsersSuccessResponse && inviteUsersErrorResponse && (
|
||||||
className="error-message"
|
<div className="success-message-container invite-users-success-message-container">
|
||||||
type="danger"
|
{inviteUsersSuccessResponse?.map((success, index) => (
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
<Typography.Text
|
||||||
key={`${error}-${index}`}
|
className="success-message"
|
||||||
>
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
<TriangleAlert size={14} /> {error}
|
key={`${success}-${index}`}
|
||||||
</Typography.Text>
|
>
|
||||||
))}
|
<CheckCircle size={14} /> {success}
|
||||||
</div>
|
</Typography.Text>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="error-message-container invite-users-error-message-container">
|
||||||
|
{inviteUsersErrorResponse?.map((error, index) => (
|
||||||
|
<Typography.Text
|
||||||
|
className="error-message"
|
||||||
|
type="danger"
|
||||||
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
key={`${error}-${index}`}
|
||||||
|
>
|
||||||
|
<TriangleAlert size={14} /> {error}
|
||||||
|
</Typography.Text>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -413,17 +416,23 @@ function InviteTeamMembers({
|
|||||||
type="primary"
|
type="primary"
|
||||||
className="next-button"
|
className="next-button"
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
loading={isSendingInvites || disableNextButton}
|
loading={isSendingInvites || isLoading || disableNextButton}
|
||||||
>
|
>
|
||||||
{allInvitesSent ? 'Invites Sent' : 'Send Invites'}
|
Send Invites
|
||||||
|
<ArrowRight size={14} />
|
||||||
{allInvitesSent ? <CheckCircle size={14} /> : <ArrowRight size={14} />}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="do-later-container">
|
<div className="do-later-container">
|
||||||
<Button type="link" onClick={handleDoLater}>
|
<Button
|
||||||
I'll do this later
|
type="link"
|
||||||
|
className="do-later-button"
|
||||||
|
onClick={handleDoLater}
|
||||||
|
disabled={isSendingInvites || disableNextButton}
|
||||||
|
>
|
||||||
|
{isLoading && <Loader2 className="animate-spin" size={16} />}
|
||||||
|
|
||||||
|
<span>I'll do this later</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -189,6 +189,15 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
|
|
||||||
|
.do-later-button {
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.question {
|
.question {
|
||||||
|
@ -314,7 +314,7 @@ function OptimiseSignozNeeds({
|
|||||||
|
|
||||||
<div className="do-later-container">
|
<div className="do-later-container">
|
||||||
<Button type="link" onClick={handleWillDoLater}>
|
<Button type="link" onClick={handleWillDoLater}>
|
||||||
Skip for now
|
I'll do this later
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,31 +1,24 @@
|
|||||||
import './OnboardingQuestionaire.styles.scss';
|
import './OnboardingQuestionaire.styles.scss';
|
||||||
|
|
||||||
import { Skeleton } from 'antd';
|
|
||||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||||
import logEvent from 'api/common/logEvent';
|
|
||||||
import updateProfileAPI from 'api/onboarding/updateProfile';
|
import updateProfileAPI from 'api/onboarding/updateProfile';
|
||||||
import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences';
|
import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences';
|
||||||
import getOrgPreference from 'api/preferences/getOrgPreference';
|
|
||||||
import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference';
|
import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference';
|
||||||
import getOrgUser from 'api/user/getOrgUser';
|
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer';
|
import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { isEmpty } from 'lodash-es';
|
import { useEffect, useState } from 'react';
|
||||||
import { Dispatch, useEffect, useState } from 'react';
|
|
||||||
import { useMutation, useQuery } from 'react-query';
|
import { useMutation, useQuery } from 'react-query';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppActions from 'types/actions';
|
|
||||||
import {
|
import {
|
||||||
UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||||
UPDATE_ORG_PREFERENCES,
|
UPDATE_ORG_PREFERENCES,
|
||||||
} from 'types/actions/app';
|
} from 'types/actions/app';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { USER_ROLES } from 'types/roles';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AboutSigNozQuestions,
|
AboutSigNozQuestions,
|
||||||
@ -70,15 +63,14 @@ const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = {
|
|||||||
|
|
||||||
function OnboardingQuestionaire(): JSX.Element {
|
function OnboardingQuestionaire(): JSX.Element {
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
const { org, role, isLoggedIn: isLoggedInState } = useSelector<
|
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
AppState,
|
const dispatch = useDispatch();
|
||||||
AppReducer
|
|
||||||
>((state) => state.app);
|
|
||||||
const [currentStep, setCurrentStep] = useState<number>(1);
|
const [currentStep, setCurrentStep] = useState<number>(1);
|
||||||
const [orgDetails, setOrgDetails] = useState<OrgDetails>(INITIAL_ORG_DETAILS);
|
const [orgDetails, setOrgDetails] = useState<OrgDetails>(INITIAL_ORG_DETAILS);
|
||||||
const [signozDetails, setSignozDetails] = useState<SignozDetails>(
|
const [signozDetails, setSignozDetails] = useState<SignozDetails>(
|
||||||
INITIAL_SIGNOZ_DETAILS,
|
INITIAL_SIGNOZ_DETAILS,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
optimiseSignozDetails,
|
optimiseSignozDetails,
|
||||||
setOptimiseSignozDetails,
|
setOptimiseSignozDetails,
|
||||||
@ -87,113 +79,12 @@ function OnboardingQuestionaire(): JSX.Element {
|
|||||||
InviteTeamMembersProps[] | null
|
InviteTeamMembersProps[] | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({
|
|
||||||
queryFn: () =>
|
|
||||||
getOrgUser({
|
|
||||||
orgId: (org || [])[0].id,
|
|
||||||
}),
|
|
||||||
queryKey: ['getOrgUser', org?.[0].id],
|
|
||||||
});
|
|
||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
|
||||||
const [currentOrgData, setCurrentOrgData] = useState<OrgData | null>(null);
|
const [currentOrgData, setCurrentOrgData] = useState<OrgData | null>(null);
|
||||||
const [isOnboardingComplete, setIsOnboardingComplete] = useState<boolean>(
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
const [
|
||||||
data: onboardingPreferenceData,
|
updatingOrgOnboardingStatus,
|
||||||
isLoading: isLoadingOnboardingPreference,
|
setUpdatingOrgOnboardingStatus,
|
||||||
} = useQuery({
|
] = useState<boolean>(false);
|
||||||
queryFn: () => getOrgPreference({ preferenceID: 'ORG_ONBOARDING' }),
|
|
||||||
queryKey: ['getOrgPreferences', 'ORG_ONBOARDING'],
|
|
||||||
enabled: role === USER_ROLES.ADMIN,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({
|
|
||||||
queryFn: () => getAllOrgPreferences(),
|
|
||||||
queryKey: ['getOrgPreferences'],
|
|
||||||
enabled: isOnboardingComplete && role === USER_ROLES.ADMIN,
|
|
||||||
});
|
|
||||||
|
|
||||||
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]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isLoggedInState && role !== USER_ROLES.ADMIN) {
|
|
||||||
dispatch({
|
|
||||||
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
|
||||||
payload: {
|
|
||||||
isFetchingOrgPreferences: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isLoggedInState, role, dispatch]);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [onboardingPreferenceData, isLoadingOnboardingPreference]);
|
|
||||||
|
|
||||||
const checkFirstTimeUser = (): boolean => {
|
|
||||||
const users = orgUsers?.payload || [];
|
|
||||||
|
|
||||||
const remainingUsers = users.filter(
|
|
||||||
(user) => user.email !== 'admin@signoz.cloud',
|
|
||||||
);
|
|
||||||
|
|
||||||
return remainingUsers.length === 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Only run this effect if the org users and preferences are loaded
|
|
||||||
if (!isLoadingOrgUsers && !isLoadingOnboardingPreference) {
|
|
||||||
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.GET_STARTED);
|
|
||||||
|
|
||||||
logEvent('User Onboarding: Redirected to Get Started', {
|
|
||||||
isFirstUser,
|
|
||||||
isOnboardingComplete,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logEvent('User Onboarding: Started', {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [
|
|
||||||
isLoadingOrgUsers,
|
|
||||||
isLoadingOnboardingPreference,
|
|
||||||
isOnboardingComplete,
|
|
||||||
orgUsers,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (org) {
|
if (org) {
|
||||||
@ -207,6 +98,35 @@ function OnboardingQuestionaire(): JSX.Element {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [org]);
|
}, [org]);
|
||||||
|
|
||||||
|
const { refetch: refetchOrgPreferences } = useQuery({
|
||||||
|
queryFn: () => getAllOrgPreferences(),
|
||||||
|
queryKey: ['getOrgPreferences'],
|
||||||
|
enabled: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
onSuccess: (response) => {
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_IS_FETCHING_ORG_PREFERENCES,
|
||||||
|
payload: {
|
||||||
|
isFetchingOrgPreferences: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: UPDATE_ORG_PREFERENCES,
|
||||||
|
payload: {
|
||||||
|
orgPreferences: response.payload?.data || null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setUpdatingOrgOnboardingStatus(false);
|
||||||
|
|
||||||
|
history.push(ROUTES.GET_STARTED);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
setUpdatingOrgOnboardingStatus(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const isNextDisabled =
|
const isNextDisabled =
|
||||||
optimiseSignozDetails.logsPerDay === 0 &&
|
optimiseSignozDetails.logsPerDay === 0 &&
|
||||||
optimiseSignozDetails.hostsPerDay === 0 &&
|
optimiseSignozDetails.hostsPerDay === 0 &&
|
||||||
@ -226,10 +146,12 @@ function OnboardingQuestionaire(): JSX.Element {
|
|||||||
|
|
||||||
const { mutate: updateOrgPreference } = useMutation(updateOrgPreferenceAPI, {
|
const { mutate: updateOrgPreference } = useMutation(updateOrgPreferenceAPI, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
setIsOnboardingComplete(true);
|
refetchOrgPreferences();
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showErrorNotification(notifications, error as AxiosError);
|
showErrorNotification(notifications, error as AxiosError);
|
||||||
|
|
||||||
|
setUpdatingOrgOnboardingStatus(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -258,6 +180,7 @@ function OnboardingQuestionaire(): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOnboardingComplete = (): void => {
|
const handleOnboardingComplete = (): void => {
|
||||||
|
setUpdatingOrgOnboardingStatus(true);
|
||||||
updateOrgPreference({
|
updateOrgPreference({
|
||||||
preferenceID: 'ORG_ONBOARDING',
|
preferenceID: 'ORG_ONBOARDING',
|
||||||
value: true,
|
value: true,
|
||||||
@ -271,55 +194,46 @@ function OnboardingQuestionaire(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="onboarding-questionaire-content">
|
<div className="onboarding-questionaire-content">
|
||||||
{(isLoadingOnboardingPreference || isLoadingOrgUsers) && (
|
{currentStep === 1 && (
|
||||||
<div className="onboarding-questionaire-loading-container">
|
<OrgQuestions
|
||||||
<Skeleton />
|
currentOrgData={currentOrgData}
|
||||||
</div>
|
orgDetails={orgDetails}
|
||||||
|
onNext={(orgDetails: OrgDetails): void => {
|
||||||
|
setOrgDetails(orgDetails);
|
||||||
|
setCurrentStep(2);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLoadingOnboardingPreference && !isLoadingOrgUsers && (
|
{currentStep === 2 && (
|
||||||
<>
|
<AboutSigNozQuestions
|
||||||
{currentStep === 1 && (
|
signozDetails={signozDetails}
|
||||||
<OrgQuestions
|
setSignozDetails={setSignozDetails}
|
||||||
currentOrgData={currentOrgData}
|
onBack={(): void => setCurrentStep(1)}
|
||||||
orgDetails={orgDetails}
|
onNext={(): void => setCurrentStep(3)}
|
||||||
onNext={(orgDetails: OrgDetails): void => {
|
/>
|
||||||
setOrgDetails(orgDetails);
|
)}
|
||||||
setCurrentStep(2);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentStep === 2 && (
|
{currentStep === 3 && (
|
||||||
<AboutSigNozQuestions
|
<OptimiseSignozNeeds
|
||||||
signozDetails={signozDetails}
|
isNextDisabled={isNextDisabled}
|
||||||
setSignozDetails={setSignozDetails}
|
isUpdatingProfile={isUpdatingProfile}
|
||||||
onBack={(): void => setCurrentStep(1)}
|
optimiseSignozDetails={optimiseSignozDetails}
|
||||||
onNext={(): void => setCurrentStep(3)}
|
setOptimiseSignozDetails={setOptimiseSignozDetails}
|
||||||
/>
|
onBack={(): void => setCurrentStep(2)}
|
||||||
)}
|
onNext={handleUpdateProfile}
|
||||||
|
onWillDoLater={(): void => setCurrentStep(4)} // This is temporary, only to skip gateway api call as it's not setup on staging yet
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{currentStep === 3 && (
|
{currentStep === 4 && (
|
||||||
<OptimiseSignozNeeds
|
<InviteTeamMembers
|
||||||
isNextDisabled={isNextDisabled}
|
isLoading={updatingOrgOnboardingStatus}
|
||||||
isUpdatingProfile={isUpdatingProfile}
|
teamMembers={teamMembers}
|
||||||
optimiseSignozDetails={optimiseSignozDetails}
|
setTeamMembers={setTeamMembers}
|
||||||
setOptimiseSignozDetails={setOptimiseSignozDetails}
|
onBack={(): void => setCurrentStep(3)}
|
||||||
onBack={(): void => setCurrentStep(2)}
|
onNext={handleOnboardingComplete}
|
||||||
onNext={handleUpdateProfile}
|
/>
|
||||||
onWillDoLater={(): void => setCurrentStep(4)} // This is temporary, only to skip gateway api call as it's not setup on staging yet
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentStep === 4 && (
|
|
||||||
<InviteTeamMembers
|
|
||||||
teamMembers={teamMembers}
|
|
||||||
setTeamMembers={setTeamMembers}
|
|
||||||
onBack={(): void => setCurrentStep(3)}
|
|
||||||
onNext={handleOnboardingComplete}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -261,7 +261,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
|||||||
values,
|
values,
|
||||||
async (): Promise<void> => {
|
async (): Promise<void> => {
|
||||||
if (isOnboardingEnabled && isCloudUser()) {
|
if (isOnboardingEnabled && isCloudUser()) {
|
||||||
history.push(ROUTES.ONBOARDING);
|
history.push(ROUTES.GET_STARTED);
|
||||||
} else {
|
} else {
|
||||||
history.push(ROUTES.APPLICATION);
|
history.push(ROUTES.APPLICATION);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user