diff --git a/frontend/src/AppRoutes/Private.tsx b/frontend/src/AppRoutes/Private.tsx index 3956676ec7..645c28095c 100644 --- a/frontend/src/AppRoutes/Private.tsx +++ b/frontend/src/AppRoutes/Private.tsx @@ -9,7 +9,7 @@ import ROUTES from 'constants/routes'; import useLicense from 'hooks/useLicense'; import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; -import { isEmpty } from 'lodash-es'; +import { isEmpty, isNull } from 'lodash-es'; import { ReactChild, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useQuery } from 'react-query'; @@ -35,13 +35,18 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { const location = useLocation(); const { pathname } = location; - const { org, orgPreferences } = useSelector( - (state) => state.app, - ); + const [isLoading, setIsLoading] = useState(true); - const [isOnboardingComplete, setIsOnboardingComplete] = useState< - boolean | null - >(null); + const { + org, + orgPreferences, + user, + role, + isUserFetching, + isUserFetchingError, + isLoggedIn: isLoggedInState, + isFetchingOrgPreferences, + } = useSelector((state) => state.app); const mapRoutes = useMemo( () => @@ -56,30 +61,19 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { [pathname], ); - useEffect(() => { - if (orgPreferences && !isEmpty(orgPreferences)) { - const onboardingPreference = orgPreferences?.find( + const isOnboardingComplete = useMemo( + () => + orgPreferences?.find( (preference: Record) => preference.key === 'ORG_ONBOARDING', - ); - - if (onboardingPreference) { - setIsOnboardingComplete(onboardingPreference.value); - } - } - }, [orgPreferences]); + )?.value, + [orgPreferences], + ); const { data: licensesData, isFetching: isFetchingLicensesData, } = useLicense(); - const { - isUserFetching, - isUserFetchingError, - isLoggedIn: isLoggedInState, - isFetchingOrgPreferences, - } = useSelector((state) => state.app); - const { t } = useTranslation(['common']); 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 const shouldShowOnboarding = (): boolean => { // Only run this effect if the org users and preferences are loaded - if (!isLoadingOrgUsers) { + + if (!isLoadingOrgUsers && !isFetchingOrgPreferences) { const isFirstUser = checkFirstTimeUser(); // 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; }; + 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 ( key: keyof typeof ROUTES, ): Promise => { @@ -166,15 +181,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { response.payload.refreshJwt, ); - const showOnboarding = shouldShowOnboarding(); - - if ( - userResponse && - showOnboarding && - userResponse.payload.role === 'ADMIN' - ) { - history.push(ROUTES.ONBOARDING); - } + handleRedirectForOrgOnboarding(key); if ( userResponse && @@ -203,7 +210,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { ) { handleUserLoginIfTokenPresent(key); } else { - // user does have localstorage values + handleRedirectForOrgOnboarding(key); navigateToLoginIfNotLoggedIn(isLocalStorageLoggedIn); } @@ -241,9 +248,9 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { }, [org]); const handleRouting = (): void => { - const showOnboarding = shouldShowOnboarding(); + const showOrgOnboarding = shouldShowOnboarding(); - if (showOnboarding) { + if (showOrgOnboarding && !isOnboardingComplete) { history.push(ROUTES.ONBOARDING); } else { history.push(ROUTES.APPLICATION); @@ -251,17 +258,27 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { }; useEffect(() => { - // Only run this effect if the org users and preferences are loaded - if (!isLoadingOrgUsers && isOnboardingComplete !== null) { - const isFirstUser = checkFirstTimeUser(); + const { isPrivate } = currentRoute || { + isPrivate: false, + }; - // Redirect to get started if it's not the first user or if the onboarding is complete - if (isFirstUser && !isOnboardingComplete) { - history.push(ROUTES.ONBOARDING); - } + if (isLoggedInState && role && role !== 'ADMIN') { + setIsLoading(false); } - // 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 useEffect(() => { @@ -284,7 +301,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { handlePrivateRoutes(key); } else { // no need to fetch the user and make user fetching false - if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') { handleRouting(); } @@ -311,13 +327,20 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { history.push(ROUTES.SOMETHING_WENT_WRONG); } })(); - }, [dispatch, isLoggedInState, currentRoute, licensesData]); + }, [ + dispatch, + isLoggedInState, + currentRoute, + licensesData, + orgUsers, + orgPreferences, + ]); if (isUserFetchingError) { return ; } - if (isUserFetching || (isLoggedInState && isFetchingOrgPreferences)) { + if (isUserFetching || isLoading) { return ; } diff --git a/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx b/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx index c1275ff115..861786f2aa 100644 --- a/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx +++ b/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx @@ -312,7 +312,7 @@ export default function Onboarding(): JSX.Element {
{ logEvent('Onboarding V2: Skip Button Clicked', {}); - history.push('/'); + history.push(ROUTES.APPLICATION); }} className="skip-to-console" > diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx index 0f99dd315b..fef689de3a 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx @@ -10,6 +10,7 @@ import { ArrowLeft, ArrowRight, CheckCircle, + Loader2, Plus, TriangleAlert, X, @@ -33,6 +34,7 @@ interface TeamMember { } interface InviteTeamMembersProps { + isLoading: boolean; teamMembers: TeamMember[] | null; setTeamMembers: (teamMembers: TeamMember[]) => void; onNext: () => void; @@ -40,6 +42,7 @@ interface InviteTeamMembersProps { } function InviteTeamMembers({ + isLoading, teamMembers, setTeamMembers, onNext, @@ -67,8 +70,6 @@ function InviteTeamMembers({ const [disableNextButton, setDisableNextButton] = useState(false); - const [allInvitesSent, setAllInvitesSent] = useState(false); - const defaultTeamMember: TeamMember = { email: '', role: 'EDITOR', @@ -157,7 +158,6 @@ function InviteTeamMembers({ setError(null); setHasErrors(false); setInviteUsersErrorResponse(null); - setAllInvitesSent(true); setInviteUsersSuccessResponse(successfulInvites); @@ -358,33 +358,36 @@ function InviteTeamMembers({
)} - {inviteUsersSuccessResponse && ( -
- {inviteUsersSuccessResponse?.map((success, index) => ( - - {success} - - ))} -
- )} - {hasErrors && ( -
- {inviteUsersErrorResponse?.map((error, index) => ( - - {error} - - ))} -
+ <> + {/* show only when invites are sent successfully & partial error is present */} + {inviteUsersSuccessResponse && inviteUsersErrorResponse && ( +
+ {inviteUsersSuccessResponse?.map((success, index) => ( + + {success} + + ))} +
+ )} + +
+ {inviteUsersErrorResponse?.map((error, index) => ( + + {error} + + ))} +
+ )} @@ -413,17 +416,23 @@ function InviteTeamMembers({ type="primary" className="next-button" onClick={handleNext} - loading={isSendingInvites || disableNextButton} + loading={isSendingInvites || isLoading || disableNextButton} > - {allInvitesSent ? 'Invites Sent' : 'Send Invites'} - - {allInvitesSent ? : } + Send Invites +
-
diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss index 22cf4c6b2a..784a15bfeb 100644 --- a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss @@ -189,6 +189,15 @@ justify-content: center; align-items: center; margin-top: 24px; + + .do-later-button { + font-size: 12px; + + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + } } .question { diff --git a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx index 08177c1e27..f1be6fb8ee 100644 --- a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx @@ -314,7 +314,7 @@ function OptimiseSignozNeeds({
diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index 4cb3ecaa80..3b3ed59354 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -1,31 +1,24 @@ import './OnboardingQuestionaire.styles.scss'; -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 getOrgUser from 'api/user/getOrgUser'; import { AxiosError } from 'axios'; import { SOMETHING_WENT_WRONG } from 'constants/api'; import ROUTES from 'constants/routes'; import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer'; import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; -import { isEmpty } from 'lodash-es'; -import { Dispatch, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useMutation, useQuery } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; import { UPDATE_IS_FETCHING_ORG_PREFERENCES, UPDATE_ORG_PREFERENCES, } from 'types/actions/app'; import AppReducer from 'types/reducer/app'; -import { USER_ROLES } from 'types/roles'; import { AboutSigNozQuestions, @@ -70,15 +63,14 @@ const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = { function OnboardingQuestionaire(): JSX.Element { const { notifications } = useNotifications(); - const { org, role, isLoggedIn: isLoggedInState } = useSelector< - AppState, - AppReducer - >((state) => state.app); + const { org } = useSelector((state) => state.app); + const dispatch = useDispatch(); const [currentStep, setCurrentStep] = useState(1); const [orgDetails, setOrgDetails] = useState(INITIAL_ORG_DETAILS); const [signozDetails, setSignozDetails] = useState( INITIAL_SIGNOZ_DETAILS, ); + const [ optimiseSignozDetails, setOptimiseSignozDetails, @@ -87,113 +79,12 @@ function OnboardingQuestionaire(): JSX.Element { InviteTeamMembersProps[] | null >(null); - const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({ - queryFn: () => - getOrgUser({ - orgId: (org || [])[0].id, - }), - queryKey: ['getOrgUser', org?.[0].id], - }); - - const dispatch = useDispatch>(); const [currentOrgData, setCurrentOrgData] = useState(null); - const [isOnboardingComplete, setIsOnboardingComplete] = useState( - false, - ); - const { - data: onboardingPreferenceData, - isLoading: isLoadingOnboardingPreference, - } = useQuery({ - 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, - ]); + const [ + updatingOrgOnboardingStatus, + setUpdatingOrgOnboardingStatus, + ] = useState(false); useEffect(() => { if (org) { @@ -207,6 +98,35 @@ function OnboardingQuestionaire(): JSX.Element { // eslint-disable-next-line react-hooks/exhaustive-deps }, [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 = optimiseSignozDetails.logsPerDay === 0 && optimiseSignozDetails.hostsPerDay === 0 && @@ -226,10 +146,12 @@ function OnboardingQuestionaire(): JSX.Element { const { mutate: updateOrgPreference } = useMutation(updateOrgPreferenceAPI, { onSuccess: () => { - setIsOnboardingComplete(true); + refetchOrgPreferences(); }, onError: (error) => { showErrorNotification(notifications, error as AxiosError); + + setUpdatingOrgOnboardingStatus(false); }, }); @@ -258,6 +180,7 @@ function OnboardingQuestionaire(): JSX.Element { }; const handleOnboardingComplete = (): void => { + setUpdatingOrgOnboardingStatus(true); updateOrgPreference({ preferenceID: 'ORG_ONBOARDING', value: true, @@ -271,55 +194,46 @@ function OnboardingQuestionaire(): JSX.Element {
- {(isLoadingOnboardingPreference || isLoadingOrgUsers) && ( -
- -
+ {currentStep === 1 && ( + { + setOrgDetails(orgDetails); + setCurrentStep(2); + }} + /> )} - {!isLoadingOnboardingPreference && !isLoadingOrgUsers && ( - <> - {currentStep === 1 && ( - { - setOrgDetails(orgDetails); - setCurrentStep(2); - }} - /> - )} + {currentStep === 2 && ( + setCurrentStep(1)} + onNext={(): void => setCurrentStep(3)} + /> + )} - {currentStep === 2 && ( - setCurrentStep(1)} - onNext={(): void => setCurrentStep(3)} - /> - )} + {currentStep === 3 && ( + 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 && ( - 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 === 4 && ( - setCurrentStep(3)} - onNext={handleOnboardingComplete} - /> - )} - + {currentStep === 4 && ( + setCurrentStep(3)} + onNext={handleOnboardingComplete} + /> )}
diff --git a/frontend/src/pages/SignUp/SignUp.tsx b/frontend/src/pages/SignUp/SignUp.tsx index 68f9c19dd1..4917b0fe2d 100644 --- a/frontend/src/pages/SignUp/SignUp.tsx +++ b/frontend/src/pages/SignUp/SignUp.tsx @@ -261,7 +261,7 @@ function SignUp({ version }: SignUpProps): JSX.Element { values, async (): Promise => { if (isOnboardingEnabled && isCloudUser()) { - history.push(ROUTES.ONBOARDING); + history.push(ROUTES.GET_STARTED); } else { history.push(ROUTES.APPLICATION); }