mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-18 02:35:57 +08:00
feat: handle invite user flows
This commit is contained in:
parent
44f41c55f9
commit
a1090bfdc5
@ -1,10 +1,10 @@
|
|||||||
import axios from 'api';
|
import axios from 'api';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
import { PayloadProps, UsersProps } from 'types/api/user/inviteUsers';
|
import { InviteUsersResponse, UsersProps } from 'types/api/user/inviteUsers';
|
||||||
|
|
||||||
const inviteUsers = async (
|
const inviteUsers = async (
|
||||||
users: UsersProps,
|
users: UsersProps,
|
||||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
): Promise<SuccessResponse<InviteUsersResponse>> => {
|
||||||
const response = await axios.post(`/invite/bulk`, users);
|
const response = await axios.post(`/invite/bulk`, users);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -82,7 +82,7 @@ export function AboutSigNozQuestions({
|
|||||||
otherInterestInSignoz,
|
otherInterestInSignoz,
|
||||||
});
|
});
|
||||||
|
|
||||||
logEvent('Onboarding: SigNoz Questions: Next', {
|
logEvent('User Onboarding: About SigNoz Questions Answered', {
|
||||||
hearAboutSignoz,
|
hearAboutSignoz,
|
||||||
otherAboutSignoz,
|
otherAboutSignoz,
|
||||||
interestInSignoz,
|
interestInSignoz,
|
||||||
@ -100,13 +100,6 @@ export function AboutSigNozQuestions({
|
|||||||
otherInterestInSignoz,
|
otherInterestInSignoz,
|
||||||
});
|
});
|
||||||
|
|
||||||
logEvent('Onboarding: SigNoz Questions: Back', {
|
|
||||||
hearAboutSignoz,
|
|
||||||
otherAboutSignoz,
|
|
||||||
interestInSignoz,
|
|
||||||
otherInterestInSignoz,
|
|
||||||
});
|
|
||||||
|
|
||||||
onBack();
|
onBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,24 +29,57 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.questions-form-container {
|
.questions-form-container {
|
||||||
.error-message-container {
|
.error-message-container,
|
||||||
padding: 16px;
|
.success-message-container,
|
||||||
margin-top: 16px;
|
.partially-sent-invites-container {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid var(--bg-slate-500, #161922);
|
|
||||||
background: var(--bg-ink-400, #121317);
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.error-message {
|
.error-message,
|
||||||
|
.success-message {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-users-error-message-container,
|
||||||
|
.invite-users-success-message-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
color: var(--bg-success-500, #00b37e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.partially-sent-invites-container {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #1d212d;
|
||||||
|
background-color: #121317;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.partially-sent-invites-message {
|
||||||
|
color: var(--bg-warning-500, #fbbd23);
|
||||||
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import './InviteTeamMembers.styles.scss';
|
|||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Button, Input, Select, Typography } from 'antd';
|
import { Button, Input, Select, Typography } from 'antd';
|
||||||
|
import logEvent from 'api/common/logEvent';
|
||||||
import inviteUsers from 'api/user/inviteUsers';
|
import inviteUsers from 'api/user/inviteUsers';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { cloneDeep, debounce, isEmpty } from 'lodash-es';
|
import { cloneDeep, debounce, isEmpty } from 'lodash-es';
|
||||||
@ -15,7 +16,12 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
import { ErrorResponse } from 'types/api';
|
import { SuccessResponse } from 'types/api';
|
||||||
|
import {
|
||||||
|
FailedInvite,
|
||||||
|
InviteUsersResponse,
|
||||||
|
SuccessfulInvite,
|
||||||
|
} from 'types/api/user/inviteUsers';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
interface TeamMember {
|
interface TeamMember {
|
||||||
@ -46,8 +52,23 @@ function InviteTeamMembers({
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
const [hasInvalidEmails, setHasInvalidEmails] = useState<boolean>(false);
|
const [hasInvalidEmails, setHasInvalidEmails] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [hasErrors, setHasErrors] = useState<boolean>(true);
|
||||||
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [inviteUsersErrorResponse, setInviteUsersErrorResponse] = useState<
|
||||||
|
string[] | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
const [inviteUsersSuccessResponse, setInviteUsersSuccessResponse] = useState<
|
||||||
|
string[] | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
const [disableNextButton, setDisableNextButton] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [allInvitesSent, setAllInvitesSent] = useState<boolean>(false);
|
||||||
|
|
||||||
const defaultTeamMember: TeamMember = {
|
const defaultTeamMember: TeamMember = {
|
||||||
email: '',
|
email: '',
|
||||||
role: 'EDITOR',
|
role: 'EDITOR',
|
||||||
@ -100,30 +121,100 @@ function InviteTeamMembers({
|
|||||||
return isValid;
|
return isValid;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleError = (error: AxiosError): void => {
|
const parseInviteUsersSuccessResponse = (
|
||||||
const errorMessage = error.response?.data as ErrorResponse;
|
response: SuccessfulInvite[],
|
||||||
|
): string[] => response.map((invite) => `${invite.email} - Invite Sent`);
|
||||||
|
|
||||||
setError(errorMessage.error);
|
const parseInviteUsersErrorResponse = (response: FailedInvite[]): string[] =>
|
||||||
|
response.map((invite) => `${invite.email} - ${invite.error}`);
|
||||||
|
|
||||||
|
const handleError = (error: AxiosError): void => {
|
||||||
|
const errorMessage = error.response?.data as InviteUsersResponse;
|
||||||
|
|
||||||
|
if (errorMessage?.status === 'failure') {
|
||||||
|
setHasErrors(true);
|
||||||
|
|
||||||
|
const failedInvitesErrorResponse = parseInviteUsersErrorResponse(
|
||||||
|
errorMessage.failed_invites,
|
||||||
|
);
|
||||||
|
|
||||||
|
setInviteUsersErrorResponse(failedInvitesErrorResponse);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const { mutate: sendInvites, isLoading: isSendingInvites } = useMutation(
|
const handleInviteUsersSuccess = (
|
||||||
inviteUsers,
|
response: SuccessResponse<InviteUsersResponse>,
|
||||||
{
|
): void => {
|
||||||
onSuccess: (): void => {
|
const inviteUsersResponse = response.payload as InviteUsersResponse;
|
||||||
|
|
||||||
|
if (inviteUsersResponse?.status === 'success') {
|
||||||
|
const successfulInvites = parseInviteUsersSuccessResponse(
|
||||||
|
inviteUsersResponse.successful_invites,
|
||||||
|
);
|
||||||
|
|
||||||
|
setDisableNextButton(true);
|
||||||
|
|
||||||
|
setError(null);
|
||||||
|
setHasErrors(false);
|
||||||
|
setInviteUsersErrorResponse(null);
|
||||||
|
setAllInvitesSent(true);
|
||||||
|
|
||||||
|
setInviteUsersSuccessResponse(successfulInvites);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setDisableNextButton(false);
|
||||||
onNext();
|
onNext();
|
||||||
},
|
}, 1000);
|
||||||
onError: (error): void => {
|
} else if (inviteUsersResponse?.status === 'partial_success') {
|
||||||
handleError(error as AxiosError);
|
const successfulInvites = parseInviteUsersSuccessResponse(
|
||||||
},
|
inviteUsersResponse.successful_invites,
|
||||||
|
);
|
||||||
|
|
||||||
|
setInviteUsersSuccessResponse(successfulInvites);
|
||||||
|
|
||||||
|
if (inviteUsersResponse.failed_invites.length > 0) {
|
||||||
|
setHasErrors(true);
|
||||||
|
|
||||||
|
setInviteUsersErrorResponse(
|
||||||
|
parseInviteUsersErrorResponse(inviteUsersResponse.failed_invites),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
mutate: sendInvites,
|
||||||
|
isLoading: isSendingInvites,
|
||||||
|
data: inviteUsersApiResponseData,
|
||||||
|
} = useMutation(inviteUsers, {
|
||||||
|
onSuccess: (response: SuccessResponse<InviteUsersResponse>): void => {
|
||||||
|
logEvent('User Onboarding: Invite Team Members Sent', {
|
||||||
|
teamMembers: teamMembersToInvite,
|
||||||
|
});
|
||||||
|
|
||||||
|
handleInviteUsersSuccess(response);
|
||||||
},
|
},
|
||||||
);
|
onError: (error: AxiosError): void => {
|
||||||
|
console.log('error', error);
|
||||||
|
|
||||||
|
logEvent('User Onboarding: Invite Team Members Failed', {
|
||||||
|
teamMembers: teamMembersToInvite,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
|
handleError(error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const handleNext = (): void => {
|
const handleNext = (): void => {
|
||||||
if (validateAllUsers()) {
|
if (validateAllUsers()) {
|
||||||
setTeamMembers(teamMembersToInvite || []);
|
setTeamMembers(teamMembersToInvite || []);
|
||||||
|
|
||||||
setError(null);
|
|
||||||
setHasInvalidEmails(false);
|
setHasInvalidEmails(false);
|
||||||
|
setError(null);
|
||||||
|
setHasErrors(false);
|
||||||
|
setInviteUsersErrorResponse(null);
|
||||||
|
setInviteUsersSuccessResponse(null);
|
||||||
|
|
||||||
sendInvites({
|
sendInvites({
|
||||||
users: teamMembersToInvite || [],
|
users: teamMembersToInvite || [],
|
||||||
@ -165,6 +256,11 @@ function InviteTeamMembers({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDoLater = (): void => {
|
const handleDoLater = (): void => {
|
||||||
|
logEvent('User Onboarding: Invite Team Members Skipped', {
|
||||||
|
teamMembers: teamMembersToInvite,
|
||||||
|
apiResponse: inviteUsersApiResponseData,
|
||||||
|
});
|
||||||
|
|
||||||
onNext();
|
onNext();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -246,21 +342,65 @@ function InviteTeamMembers({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{hasInvalidEmails && (
|
||||||
|
<div className="error-message-container">
|
||||||
|
<Typography.Text className="error-message" type="danger">
|
||||||
|
<TriangleAlert size={14} /> Please enter valid emails for all team
|
||||||
|
members
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="error-message-container">
|
||||||
|
<Typography.Text className="error-message" type="danger">
|
||||||
|
<TriangleAlert size={14} /> {error}
|
||||||
|
</Typography.Text>
|
||||||
|
</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 && (
|
||||||
|
<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>
|
||||||
|
|
||||||
{hasInvalidEmails && (
|
{/* Partially sent invites */}
|
||||||
<div className="error-message-container">
|
{inviteUsersSuccessResponse && inviteUsersErrorResponse && (
|
||||||
<Typography.Text className="error-message" type="danger">
|
<div className="partially-sent-invites-container">
|
||||||
<TriangleAlert size={14} /> Please enter valid emails for all team
|
<Typography.Text className="partially-sent-invites-message">
|
||||||
members
|
<TriangleAlert size={14} />
|
||||||
|
Some invites were sent successfully. Please fix the errors above and
|
||||||
|
resend invites.
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{error && (
|
<Typography.Text className="partially-sent-invites-message">
|
||||||
<div className="error-message-container">
|
You can click on I'll do this later to go to next step.
|
||||||
<Typography.Text className="error-message" type="danger">
|
|
||||||
<TriangleAlert size={14} /> {error}
|
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -275,10 +415,11 @@ function InviteTeamMembers({
|
|||||||
type="primary"
|
type="primary"
|
||||||
className="next-button"
|
className="next-button"
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
loading={isSendingInvites}
|
loading={isSendingInvites || disableNextButton}
|
||||||
>
|
>
|
||||||
Send Invites
|
{allInvitesSent ? 'Invites Sent' : 'Send Invites'}
|
||||||
<ArrowRight size={14} />
|
|
||||||
|
{allInvitesSent ? <CheckCircle size={14} /> : <ArrowRight size={14} />}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -90,7 +90,6 @@
|
|||||||
|
|
||||||
.invite-team-members-container {
|
.invite-team-members-container {
|
||||||
max-height: 260px;
|
max-height: 260px;
|
||||||
padding-right: 8px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
|
@ -122,7 +122,7 @@ function OptimiseSignozNeeds({
|
|||||||
}, [services, hostsPerDay, logsPerDay]);
|
}, [services, hostsPerDay, logsPerDay]);
|
||||||
|
|
||||||
const handleOnNext = (): void => {
|
const handleOnNext = (): void => {
|
||||||
logEvent('Onboarding: Optimise SigNoz Needs: Next', {
|
logEvent('User Onboarding: Optimise SigNoz Needs Answered', {
|
||||||
logsPerDay,
|
logsPerDay,
|
||||||
hostsPerDay,
|
hostsPerDay,
|
||||||
services,
|
services,
|
||||||
@ -132,12 +132,6 @@ function OptimiseSignozNeeds({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOnBack = (): void => {
|
const handleOnBack = (): void => {
|
||||||
logEvent('Onboarding: Optimise SigNoz Needs: Back', {
|
|
||||||
logsPerDay,
|
|
||||||
hostsPerDay,
|
|
||||||
services,
|
|
||||||
});
|
|
||||||
|
|
||||||
onBack();
|
onBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -150,7 +144,7 @@ function OptimiseSignozNeeds({
|
|||||||
|
|
||||||
onWillDoLater();
|
onWillDoLater();
|
||||||
|
|
||||||
logEvent('Onboarding: Optimise SigNoz Needs: Will do later', {
|
logEvent('User Onboarding: Optimise SigNoz Needs Skipped', {
|
||||||
logsPerDay: 0,
|
logsPerDay: 0,
|
||||||
hostsPerDay: 0,
|
hostsPerDay: 0,
|
||||||
services: 0,
|
services: 0,
|
||||||
|
@ -120,7 +120,7 @@ function OrgQuestions({
|
|||||||
familiarity,
|
familiarity,
|
||||||
});
|
});
|
||||||
|
|
||||||
logEvent('Onboarding: Org Questions: Next', {
|
logEvent('User Onboarding: Org Questions Answered', {
|
||||||
organisationName,
|
organisationName,
|
||||||
usesObservability,
|
usesObservability,
|
||||||
observabilityTool,
|
observabilityTool,
|
||||||
|
@ -2,6 +2,7 @@ import './OnboardingQuestionaire.styles.scss';
|
|||||||
|
|
||||||
import { Skeleton } from 'antd';
|
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 getOrgPreference from 'api/preferences/getOrgPreference';
|
import getOrgPreference from 'api/preferences/getOrgPreference';
|
||||||
import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference';
|
import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference';
|
||||||
@ -67,7 +68,7 @@ const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = {
|
|||||||
function OnboardingQuestionaire(): JSX.Element {
|
function OnboardingQuestionaire(): JSX.Element {
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
const [currentStep, setCurrentStep] = useState<number>(1);
|
const [currentStep, setCurrentStep] = useState<number>(4);
|
||||||
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,
|
||||||
@ -91,8 +92,6 @@ function OnboardingQuestionaire(): JSX.Element {
|
|||||||
queryKey: ['getOrgUser', org?.[0].id],
|
queryKey: ['getOrgUser', org?.[0].id],
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('orgUsers', orgUsers, isLoadingOrgUsers);
|
|
||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
const [orgData, setOrgData] = useState<OrgData | null>(null);
|
const [orgData, setOrgData] = useState<OrgData | null>(null);
|
||||||
const [isOnboardingComplete, setIsOnboardingComplete] = useState<boolean>(
|
const [isOnboardingComplete, setIsOnboardingComplete] = useState<boolean>(
|
||||||
@ -126,13 +125,29 @@ function OnboardingQuestionaire(): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isFirstUser = checkFirstTimeUser();
|
// Only run this effect if the org users and preferences are loaded
|
||||||
|
if (!isLoadingOrgUsers && !isLoadingOrgPreferences) {
|
||||||
|
const isFirstUser = checkFirstTimeUser();
|
||||||
|
|
||||||
if (isOnboardingComplete || !isFirstUser) {
|
// Redirect to get started if it's not the first user or if the onboarding is complete
|
||||||
history.push(ROUTES.GET_STARTED);
|
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
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isOnboardingComplete, orgUsers]);
|
}, [
|
||||||
|
isLoadingOrgUsers,
|
||||||
|
isLoadingOrgPreferences,
|
||||||
|
isOnboardingComplete,
|
||||||
|
orgUsers,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (org) {
|
if (org) {
|
||||||
@ -182,8 +197,16 @@ function OnboardingQuestionaire(): JSX.Element {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logEvent('User Onboarding: Org Name Updated', {
|
||||||
|
organisationName: orgDetails.organisationName,
|
||||||
|
});
|
||||||
|
|
||||||
setCurrentStep(2);
|
setCurrentStep(2);
|
||||||
} else {
|
} else {
|
||||||
|
logEvent('User Onboarding: Org Name Update Failed', {
|
||||||
|
organisationName: orgDetails.organisationName,
|
||||||
|
});
|
||||||
|
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message:
|
message:
|
||||||
error ||
|
error ||
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { User } from 'types/reducer/app';
|
import { User } from 'types/reducer/app';
|
||||||
|
|
||||||
|
import { ErrorResponse } from '..';
|
||||||
|
|
||||||
export interface UserProps {
|
export interface UserProps {
|
||||||
name: User['name'];
|
name: User['name'];
|
||||||
email: User['email'];
|
email: User['email'];
|
||||||
@ -14,3 +16,25 @@ export interface UsersProps {
|
|||||||
export interface PayloadProps {
|
export interface PayloadProps {
|
||||||
data: string;
|
data: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FailedInvite {
|
||||||
|
email: string;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuccessfulInvite {
|
||||||
|
email: string;
|
||||||
|
invite_link: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InviteUsersResponse extends ErrorResponse {
|
||||||
|
status: string;
|
||||||
|
summary: {
|
||||||
|
total_invites: number;
|
||||||
|
successful_invites: number;
|
||||||
|
failed_invites: number;
|
||||||
|
};
|
||||||
|
successful_invites: SuccessfulInvite[];
|
||||||
|
failed_invites: FailedInvite[];
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user