mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-13 09:29:04 +08:00
feat: integrate update profile and invite users api
This commit is contained in:
parent
6664e1bc02
commit
6c350f30aa
@ -4,6 +4,7 @@ export const apiV2 = '/api/v2/';
|
|||||||
export const apiV3 = '/api/v3/';
|
export const apiV3 = '/api/v3/';
|
||||||
export const apiV4 = '/api/v4/';
|
export const apiV4 = '/api/v4/';
|
||||||
export const gatewayApiV1 = '/api/gateway/v1/';
|
export const gatewayApiV1 = '/api/gateway/v1/';
|
||||||
|
export const gatewayApiV2 = '/api/gateway/v2/';
|
||||||
export const apiAlertManager = '/api/alertmanager/';
|
export const apiAlertManager = '/api/alertmanager/';
|
||||||
|
|
||||||
export default apiV1;
|
export default apiV1;
|
||||||
|
@ -15,6 +15,7 @@ import apiV1, {
|
|||||||
apiV3,
|
apiV3,
|
||||||
apiV4,
|
apiV4,
|
||||||
gatewayApiV1,
|
gatewayApiV1,
|
||||||
|
gatewayApiV2,
|
||||||
} from './apiV1';
|
} from './apiV1';
|
||||||
import { Logout } from './utils';
|
import { Logout } from './utils';
|
||||||
|
|
||||||
@ -169,6 +170,19 @@ GatewayApiV1Instance.interceptors.response.use(
|
|||||||
GatewayApiV1Instance.interceptors.request.use(interceptorsRequestResponse);
|
GatewayApiV1Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// gateway Api V2
|
||||||
|
export const GatewayApiV2Instance = axios.create({
|
||||||
|
baseURL: `${ENVIRONMENT.baseURL}${gatewayApiV2}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
GatewayApiV1Instance.interceptors.response.use(
|
||||||
|
interceptorsResponse,
|
||||||
|
interceptorRejected,
|
||||||
|
);
|
||||||
|
|
||||||
|
GatewayApiV1Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||||
|
//
|
||||||
|
|
||||||
AxiosAlertManagerInstance.interceptors.response.use(
|
AxiosAlertManagerInstance.interceptors.response.use(
|
||||||
interceptorsResponse,
|
interceptorsResponse,
|
||||||
interceptorRejected,
|
interceptorRejected,
|
||||||
|
26
frontend/src/api/onboarding/updateProfile.ts
Normal file
26
frontend/src/api/onboarding/updateProfile.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { GatewayApiV2Instance } from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { UpdateProfileProps } from 'types/api/onboarding/types';
|
||||||
|
|
||||||
|
const updateProfile = async (
|
||||||
|
props: UpdateProfileProps,
|
||||||
|
): Promise<SuccessResponse<UpdateProfileProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await GatewayApiV2Instance.put('/profiles/me', {
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateProfile;
|
25
frontend/src/api/user/inviteUsers.ts
Normal file
25
frontend/src/api/user/inviteUsers.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import axios, { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { PayloadProps, UsersProps } from 'types/api/user/inviteUsers';
|
||||||
|
|
||||||
|
const inviteUsers = async (
|
||||||
|
users: UsersProps,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(`/invite/bulk`, {
|
||||||
|
...users,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default inviteUsers;
|
@ -7,9 +7,16 @@ import logEvent from 'api/common/logEvent';
|
|||||||
import { ArrowLeft, ArrowRight, CheckCircle } from 'lucide-react';
|
import { ArrowLeft, ArrowRight, CheckCircle } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export interface SignozDetails {
|
||||||
|
hearAboutSignoz: string | null;
|
||||||
|
interestInSignoz: string | null;
|
||||||
|
otherInterestInSignoz: string | null;
|
||||||
|
otherAboutSignoz: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
interface AboutSigNozQuestionsProps {
|
interface AboutSigNozQuestionsProps {
|
||||||
signozDetails: any;
|
signozDetails: SignozDetails;
|
||||||
setSignozDetails: (details: any) => void;
|
setSignozDetails: (details: SignozDetails) => void;
|
||||||
onNext: () => void;
|
onNext: () => void;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
}
|
}
|
||||||
@ -41,11 +48,11 @@ export function AboutSigNozQuestions({
|
|||||||
const [otherAboutSignoz, setOtherAboutSignoz] = useState<string>(
|
const [otherAboutSignoz, setOtherAboutSignoz] = useState<string>(
|
||||||
signozDetails?.otherAboutSignoz || '',
|
signozDetails?.otherAboutSignoz || '',
|
||||||
);
|
);
|
||||||
const [interestedSignoz, setInterestedSignoz] = useState<string | null>(
|
const [interestInSignoz, setInterestInSignoz] = useState<string | null>(
|
||||||
signozDetails?.interestedSignoz || null,
|
signozDetails?.interestInSignoz || null,
|
||||||
);
|
);
|
||||||
const [otherInterest, setOtherInterest] = useState<string>(
|
const [otherInterestInSignoz, setOtherInterestInSignoz] = useState<string>(
|
||||||
signozDetails?.otherInterest || '',
|
signozDetails?.otherInterestInSignoz || '',
|
||||||
);
|
);
|
||||||
const [isNextDisabled, setIsNextDisabled] = useState<boolean>(true);
|
const [isNextDisabled, setIsNextDisabled] = useState<boolean>(true);
|
||||||
|
|
||||||
@ -53,28 +60,33 @@ export function AboutSigNozQuestions({
|
|||||||
if (
|
if (
|
||||||
hearAboutSignoz !== null &&
|
hearAboutSignoz !== null &&
|
||||||
(hearAboutSignoz !== 'Others' || otherAboutSignoz !== '') &&
|
(hearAboutSignoz !== 'Others' || otherAboutSignoz !== '') &&
|
||||||
interestedSignoz !== null &&
|
interestInSignoz !== null &&
|
||||||
(interestedSignoz !== 'Others' || otherInterest !== '')
|
(interestInSignoz !== 'Others' || otherInterestInSignoz !== '')
|
||||||
) {
|
) {
|
||||||
setIsNextDisabled(false);
|
setIsNextDisabled(false);
|
||||||
} else {
|
} else {
|
||||||
setIsNextDisabled(true);
|
setIsNextDisabled(true);
|
||||||
}
|
}
|
||||||
}, [hearAboutSignoz, otherAboutSignoz, interestedSignoz, otherInterest]);
|
}, [
|
||||||
|
hearAboutSignoz,
|
||||||
|
otherAboutSignoz,
|
||||||
|
interestInSignoz,
|
||||||
|
otherInterestInSignoz,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleOnNext = (): void => {
|
const handleOnNext = (): void => {
|
||||||
setSignozDetails({
|
setSignozDetails({
|
||||||
hearAboutSignoz,
|
hearAboutSignoz,
|
||||||
otherAboutSignoz,
|
otherAboutSignoz,
|
||||||
interestedSignoz,
|
interestInSignoz,
|
||||||
otherInterest,
|
otherInterestInSignoz,
|
||||||
});
|
});
|
||||||
|
|
||||||
logEvent('Onboarding: SigNoz Questions: Next', {
|
logEvent('Onboarding: SigNoz Questions: Next', {
|
||||||
hearAboutSignoz,
|
hearAboutSignoz,
|
||||||
otherAboutSignoz,
|
otherAboutSignoz,
|
||||||
interestedSignoz,
|
interestInSignoz,
|
||||||
otherInterest,
|
otherInterestInSignoz,
|
||||||
});
|
});
|
||||||
|
|
||||||
onNext();
|
onNext();
|
||||||
@ -84,15 +96,15 @@ export function AboutSigNozQuestions({
|
|||||||
setSignozDetails({
|
setSignozDetails({
|
||||||
hearAboutSignoz,
|
hearAboutSignoz,
|
||||||
otherAboutSignoz,
|
otherAboutSignoz,
|
||||||
interestedSignoz,
|
interestInSignoz,
|
||||||
otherInterest,
|
otherInterestInSignoz,
|
||||||
});
|
});
|
||||||
|
|
||||||
logEvent('Onboarding: SigNoz Questions: Back', {
|
logEvent('Onboarding: SigNoz Questions: Back', {
|
||||||
hearAboutSignoz,
|
hearAboutSignoz,
|
||||||
otherAboutSignoz,
|
otherAboutSignoz,
|
||||||
interestedSignoz,
|
interestInSignoz,
|
||||||
otherInterest,
|
otherInterestInSignoz,
|
||||||
});
|
});
|
||||||
|
|
||||||
onBack();
|
onBack();
|
||||||
@ -168,40 +180,40 @@ export function AboutSigNozQuestions({
|
|||||||
key={option}
|
key={option}
|
||||||
type="primary"
|
type="primary"
|
||||||
className={`onboarding-questionaire-button ${
|
className={`onboarding-questionaire-button ${
|
||||||
interestedSignoz === option ? 'active' : ''
|
interestInSignoz === option ? 'active' : ''
|
||||||
}`}
|
}`}
|
||||||
onClick={(): void => setInterestedSignoz(option)}
|
onClick={(): void => setInterestInSignoz(option)}
|
||||||
>
|
>
|
||||||
{interestedInOptions[option]}
|
{interestedInOptions[option]}
|
||||||
{interestedSignoz === option && (
|
{interestInSignoz === option && (
|
||||||
<CheckCircle size={12} color={Color.BG_FOREST_500} />
|
<CheckCircle size={12} color={Color.BG_FOREST_500} />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{interestedSignoz === 'Others' ? (
|
{interestInSignoz === 'Others' ? (
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
className="onboarding-questionaire-other-input"
|
className="onboarding-questionaire-other-input"
|
||||||
placeholder="Please specify your interest"
|
placeholder="Please specify your interest"
|
||||||
value={otherInterest}
|
value={otherInterestInSignoz}
|
||||||
autoFocus
|
autoFocus
|
||||||
addonAfter={
|
addonAfter={
|
||||||
otherInterest !== '' ? (
|
otherInterestInSignoz !== '' ? (
|
||||||
<CheckCircle size={12} color={Color.BG_FOREST_500} />
|
<CheckCircle size={12} color={Color.BG_FOREST_500} />
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onChange={(e): void => setOtherInterest(e.target.value)}
|
onChange={(e): void => setOtherInterestInSignoz(e.target.value)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
className={`onboarding-questionaire-button ${
|
className={`onboarding-questionaire-button ${
|
||||||
interestedSignoz === 'Others' ? 'active' : ''
|
interestInSignoz === 'Others' ? 'active' : ''
|
||||||
}`}
|
}`}
|
||||||
onClick={(): void => setInterestedSignoz('Others')}
|
onClick={(): void => setInterestInSignoz('Others')}
|
||||||
>
|
>
|
||||||
Others
|
Others
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -0,0 +1,46 @@
|
|||||||
|
.team-member-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.team-member-role-select {
|
||||||
|
width: 20%;
|
||||||
|
|
||||||
|
.ant-select-selector {
|
||||||
|
border: 1px solid #1d212d;
|
||||||
|
border-top-right-radius: 0px;
|
||||||
|
border-bottom-right-radius: 0px;
|
||||||
|
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.team-member-email-input {
|
||||||
|
width: 80%;
|
||||||
|
|
||||||
|
border: 1px solid #1d212d;
|
||||||
|
border-top-left-radius: 0px;
|
||||||
|
border-bottom-left-radius: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.questions-form-container {
|
||||||
|
.error-message-container {
|
||||||
|
padding: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--bg-slate-500, #161922);
|
||||||
|
background: var(--bg-ink-400, #121317);
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
|
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 { cloneDeep, debounce, isEmpty } from 'lodash-es';
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
@ -7,55 +10,123 @@ import {
|
|||||||
Plus,
|
Plus,
|
||||||
TriangleAlert,
|
TriangleAlert,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
interface TeamMember {
|
||||||
|
email: string;
|
||||||
|
role: string;
|
||||||
|
name: string;
|
||||||
|
frontendBaseUrl: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface InviteTeamMembersProps {
|
interface InviteTeamMembersProps {
|
||||||
teamMembers: string[];
|
teamMembers: TeamMember[] | null;
|
||||||
setTeamMembers: (teamMembers: string[]) => void;
|
setTeamMembers: (teamMembers: TeamMember[]) => void;
|
||||||
onNext: () => void;
|
onNext: () => void;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userRolesOptions = (
|
|
||||||
<Select defaultValue="editor">
|
|
||||||
<Select.Option value="viewer">Viewer</Select.Option>
|
|
||||||
<Select.Option value="editor">Editor</Select.Option>
|
|
||||||
<Select.Option value="Admin">Admin</Select.Option>
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
|
|
||||||
function InviteTeamMembers({
|
function InviteTeamMembers({
|
||||||
teamMembers,
|
teamMembers,
|
||||||
setTeamMembers,
|
setTeamMembers,
|
||||||
onNext,
|
onNext,
|
||||||
onBack,
|
onBack,
|
||||||
}: InviteTeamMembersProps): JSX.Element {
|
}: InviteTeamMembersProps): JSX.Element {
|
||||||
const [teamMembersToInvite, setTeamMembersToInvite] = useState<string[]>(
|
const [teamMembersToInvite, setTeamMembersToInvite] = useState<
|
||||||
teamMembers || [''],
|
TeamMember[] | null
|
||||||
|
>(teamMembers);
|
||||||
|
|
||||||
|
const [emailValidity, setEmailValidity] = useState<Record<string, boolean>>(
|
||||||
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [hasInvalidEmails, setHasInvalidEmails] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const defaultTeamMember: TeamMember = {
|
||||||
|
email: '',
|
||||||
|
role: 'EDITOR',
|
||||||
|
name: '',
|
||||||
|
frontendBaseUrl: '',
|
||||||
|
id: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isEmpty(teamMembers)) {
|
||||||
|
const teamMember = {
|
||||||
|
...defaultTeamMember,
|
||||||
|
id: uuid(),
|
||||||
|
};
|
||||||
|
|
||||||
|
setTeamMembersToInvite([teamMember]);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [teamMembers]);
|
||||||
|
|
||||||
const handleAddTeamMember = (): void => {
|
const handleAddTeamMember = (): void => {
|
||||||
setTeamMembersToInvite([...teamMembersToInvite, '']);
|
const newTeamMember = { ...defaultTeamMember, id: uuid() };
|
||||||
|
setTeamMembersToInvite((prev) => [...(prev || []), newTeamMember]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validation function to check all users
|
||||||
|
const validateAllUsers = (): boolean => {
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
|
const updatedValidity: Record<string, boolean> = {};
|
||||||
|
|
||||||
|
teamMembersToInvite?.forEach((member) => {
|
||||||
|
const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(member.email);
|
||||||
|
if (!emailValid || !member.email) {
|
||||||
|
isValid = false;
|
||||||
|
setHasInvalidEmails(true);
|
||||||
|
}
|
||||||
|
updatedValidity[member.id!] = emailValid;
|
||||||
|
});
|
||||||
|
|
||||||
|
setEmailValidity(updatedValidity);
|
||||||
|
|
||||||
|
return isValid;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNext = (): void => {
|
const handleNext = (): void => {
|
||||||
console.log(teamMembersToInvite);
|
if (validateAllUsers()) {
|
||||||
setTeamMembers(teamMembersToInvite);
|
setTeamMembers(teamMembersToInvite || []);
|
||||||
onNext();
|
onNext();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnChange = (
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
const debouncedValidateEmail = useCallback(
|
||||||
|
debounce((email: string, memberId: string) => {
|
||||||
|
const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||||||
|
setEmailValidity((prev) => ({ ...prev, [memberId]: isValid }));
|
||||||
|
}, 500),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEmailChange = (
|
||||||
e: React.ChangeEvent<HTMLInputElement>,
|
e: React.ChangeEvent<HTMLInputElement>,
|
||||||
index: number,
|
member: TeamMember,
|
||||||
): void => {
|
): void => {
|
||||||
const newTeamMembers = [...teamMembersToInvite];
|
const { value } = e.target;
|
||||||
newTeamMembers[index] = e.target.value;
|
const updatedMembers = cloneDeep(teamMembersToInvite || []);
|
||||||
setTeamMembersToInvite(newTeamMembers);
|
|
||||||
|
const memberToUpdate = updatedMembers.find((m) => m.id === member.id);
|
||||||
|
if (memberToUpdate) {
|
||||||
|
memberToUpdate.email = value;
|
||||||
|
setTeamMembersToInvite(updatedMembers);
|
||||||
|
debouncedValidateEmail(value, member.id!);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isValidEmail = (email: string): boolean => {
|
const handleRoleChange = (role: string, member: TeamMember): void => {
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
const updatedMembers = cloneDeep(teamMembersToInvite || []);
|
||||||
return emailRegex.test(email);
|
const memberToUpdate = updatedMembers.find((m) => m.id === member.id);
|
||||||
|
if (memberToUpdate) {
|
||||||
|
memberToUpdate.role = role;
|
||||||
|
setTeamMembersToInvite(updatedMembers);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -79,29 +150,37 @@ function InviteTeamMembers({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="invite-team-members-container">
|
<div className="invite-team-members-container">
|
||||||
{teamMembersToInvite.map((member, index) => (
|
{teamMembersToInvite?.map((member) => (
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
<div className="team-member-container" key={member.id}>
|
||||||
<div className="team-member-container" key={`${member}-${index}`}>
|
<Select
|
||||||
|
defaultValue={member.role}
|
||||||
|
onChange={(value): void => handleRoleChange(value, member)}
|
||||||
|
className="team-member-role-select"
|
||||||
|
>
|
||||||
|
<Select.Option value="VIEWER">Viewer</Select.Option>
|
||||||
|
<Select.Option value="EDITOR">Editor</Select.Option>
|
||||||
|
<Select.Option value="ADMIN">Admin</Select.Option>
|
||||||
|
</Select>
|
||||||
<Input
|
<Input
|
||||||
addonBefore={userRolesOptions}
|
|
||||||
addonAfter={
|
|
||||||
// eslint-disable-next-line no-nested-ternary
|
|
||||||
member.length > 0 ? (
|
|
||||||
isValidEmail(member) ? (
|
|
||||||
<CheckCircle size={14} color={Color.BG_FOREST_500} />
|
|
||||||
) : (
|
|
||||||
<TriangleAlert size={14} color={Color.BG_SIENNA_500} />
|
|
||||||
)
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
placeholder="your-teammate@org.com"
|
placeholder="your-teammate@org.com"
|
||||||
value={member}
|
value={member.email}
|
||||||
type="email"
|
type="email"
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
className="team-member-email-input"
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>): void =>
|
onChange={(e: React.ChangeEvent<HTMLInputElement>): void =>
|
||||||
handleOnChange(e, index)
|
handleEmailChange(e, member)
|
||||||
|
}
|
||||||
|
addonAfter={
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
emailValidity[member.id!] === undefined ? null : emailValidity[
|
||||||
|
member.id!
|
||||||
|
] ? (
|
||||||
|
<CheckCircle size={14} color={Color.BG_FOREST_500} />
|
||||||
|
) : (
|
||||||
|
<TriangleAlert size={14} color={Color.BG_SIENNA_500} />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -121,6 +200,15 @@ function InviteTeamMembers({
|
|||||||
</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>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="next-prev-container">
|
<div className="next-prev-container">
|
||||||
<Button type="default" className="next-button" onClick={onBack}>
|
<Button type="default" className="next-button" onClick={onBack}>
|
||||||
<ArrowLeft size={14} />
|
<ArrowLeft size={14} />
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
import { Button, Slider, SliderSingleProps, Typography } from 'antd';
|
import { Button, Slider, SliderSingleProps, Typography } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { ArrowLeft, ArrowRight, Minus } from 'lucide-react';
|
import { ArrowLeft, ArrowRight, Loader2, Minus } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export interface OptimiseSignozDetails {
|
||||||
|
logsPerDay: number;
|
||||||
|
hostsPerDay: number;
|
||||||
|
services: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface OptimiseSignozNeedsProps {
|
interface OptimiseSignozNeedsProps {
|
||||||
optimiseSignozDetails: Record<string, number> | null;
|
optimiseSignozDetails: OptimiseSignozDetails;
|
||||||
setOptimiseSignozDetails: (details: Record<string, number> | null) => void;
|
setOptimiseSignozDetails: (details: OptimiseSignozDetails) => void;
|
||||||
onNext: () => void;
|
onNext: () => void;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
|
isUpdatingProfile: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const logMarks: SliderSingleProps['marks'] = {
|
const logMarks: SliderSingleProps['marks'] = {
|
||||||
@ -36,6 +43,7 @@ const serviceMarks: SliderSingleProps['marks'] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function OptimiseSignozNeeds({
|
function OptimiseSignozNeeds({
|
||||||
|
isUpdatingProfile,
|
||||||
optimiseSignozDetails,
|
optimiseSignozDetails,
|
||||||
setOptimiseSignozDetails,
|
setOptimiseSignozDetails,
|
||||||
onNext,
|
onNext,
|
||||||
@ -51,13 +59,16 @@ function OptimiseSignozNeeds({
|
|||||||
optimiseSignozDetails?.services || 10,
|
optimiseSignozDetails?.services || 10,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOnNext = (): void => {
|
useEffect(() => {
|
||||||
setOptimiseSignozDetails({
|
setOptimiseSignozDetails({
|
||||||
logsPerDay,
|
logsPerDay,
|
||||||
hostsPerDay,
|
hostsPerDay,
|
||||||
services,
|
services,
|
||||||
});
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [services, hostsPerDay, logsPerDay]);
|
||||||
|
|
||||||
|
const handleOnNext = (): void => {
|
||||||
logEvent('Onboarding: Optimise SigNoz Needs: Next', {
|
logEvent('Onboarding: Optimise SigNoz Needs: Next', {
|
||||||
logsPerDay,
|
logsPerDay,
|
||||||
hostsPerDay,
|
hostsPerDay,
|
||||||
@ -68,12 +79,6 @@ function OptimiseSignozNeeds({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleOnBack = (): void => {
|
const handleOnBack = (): void => {
|
||||||
setOptimiseSignozDetails({
|
|
||||||
logsPerDay,
|
|
||||||
hostsPerDay,
|
|
||||||
services,
|
|
||||||
});
|
|
||||||
|
|
||||||
logEvent('Onboarding: Optimise SigNoz Needs: Back', {
|
logEvent('Onboarding: Optimise SigNoz Needs: Back', {
|
||||||
logsPerDay,
|
logsPerDay,
|
||||||
hostsPerDay,
|
hostsPerDay,
|
||||||
@ -95,8 +100,6 @@ function OptimiseSignozNeeds({
|
|||||||
hostsPerDay: 0,
|
hostsPerDay: 0,
|
||||||
services: 0,
|
services: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
onNext();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -122,7 +125,7 @@ function OptimiseSignozNeeds({
|
|||||||
<Slider
|
<Slider
|
||||||
marks={logMarks}
|
marks={logMarks}
|
||||||
defaultValue={logsPerDay}
|
defaultValue={logsPerDay}
|
||||||
onChange={(value): void => setLogsPerDay(value)}
|
onAfterChange={(value): void => setLogsPerDay(value)}
|
||||||
styles={{
|
styles={{
|
||||||
track: {
|
track: {
|
||||||
background: '#4E74F8',
|
background: '#4E74F8',
|
||||||
@ -140,7 +143,7 @@ function OptimiseSignozNeeds({
|
|||||||
<Slider
|
<Slider
|
||||||
marks={hostMarks}
|
marks={hostMarks}
|
||||||
defaultValue={hostsPerDay}
|
defaultValue={hostsPerDay}
|
||||||
onChange={(value): void => setHostsPerDay(value)}
|
onAfterChange={(value: number): void => setHostsPerDay(value)}
|
||||||
styles={{
|
styles={{
|
||||||
track: {
|
track: {
|
||||||
background: '#4E74F8',
|
background: '#4E74F8',
|
||||||
@ -158,7 +161,7 @@ function OptimiseSignozNeeds({
|
|||||||
<Slider
|
<Slider
|
||||||
marks={serviceMarks}
|
marks={serviceMarks}
|
||||||
defaultValue={services}
|
defaultValue={services}
|
||||||
onChange={(value): void => setServices(value)}
|
onAfterChange={(value): void => setServices(value)}
|
||||||
styles={{
|
styles={{
|
||||||
track: {
|
track: {
|
||||||
background: '#4E74F8',
|
background: '#4E74F8',
|
||||||
@ -170,14 +173,28 @@ function OptimiseSignozNeeds({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="next-prev-container">
|
<div className="next-prev-container">
|
||||||
<Button type="default" className="next-button" onClick={handleOnBack}>
|
<Button
|
||||||
|
type="default"
|
||||||
|
className="next-button"
|
||||||
|
onClick={handleOnBack}
|
||||||
|
disabled={isUpdatingProfile}
|
||||||
|
>
|
||||||
<ArrowLeft size={14} />
|
<ArrowLeft size={14} />
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button type="primary" className="next-button" onClick={handleOnNext}>
|
<Button
|
||||||
Next
|
type="primary"
|
||||||
<ArrowRight size={14} />
|
className="next-button"
|
||||||
|
onClick={handleOnNext}
|
||||||
|
disabled={isUpdatingProfile}
|
||||||
|
>
|
||||||
|
Next{' '}
|
||||||
|
{isUpdatingProfile ? (
|
||||||
|
<Loader2 className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
<ArrowRight size={14} />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -4,27 +4,42 @@ import '../OnboardingQuestionaire.styles.scss';
|
|||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Button, Input, Typography } from 'antd';
|
import { Button, Input, Typography } from 'antd';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { ArrowRight, CheckCircle } from 'lucide-react';
|
import { ArrowRight, CheckCircle, Loader2 } from 'lucide-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
|
export interface OrgData {
|
||||||
|
id: string;
|
||||||
|
isAnonymous: boolean;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrgDetails {
|
||||||
|
organisationName: string;
|
||||||
|
usesObservability: boolean | null;
|
||||||
|
observabilityTool: string | null;
|
||||||
|
otherTool: string | null;
|
||||||
|
familiarity: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
interface OrgQuestionsProps {
|
interface OrgQuestionsProps {
|
||||||
orgDetails: any;
|
isLoading: boolean;
|
||||||
setOrgDetails: (details: any) => void;
|
orgDetails: OrgDetails;
|
||||||
|
setOrgDetails: (details: OrgDetails) => void;
|
||||||
onNext: () => void;
|
onNext: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const observabilityTools = [
|
const observabilityTools = {
|
||||||
'AWS Cloudwatch',
|
AWSCloudwatch: 'AWS Cloudwatch',
|
||||||
'DataDog',
|
DataDog: 'DataDog',
|
||||||
'New Relic',
|
NewRelic: 'New Relic',
|
||||||
'Grafana / Prometheus',
|
GrafanaPrometheus: 'Grafana / Prometheus',
|
||||||
'Azure App Monitor',
|
AzureAppMonitor: 'Azure App Monitor',
|
||||||
'GCP-native o11y tools',
|
GCPNativeO11yTools: 'GCP-native o11y tools',
|
||||||
'Honeycomb',
|
Honeycomb: 'Honeycomb',
|
||||||
];
|
};
|
||||||
|
|
||||||
const o11yFamiliarityOptions: Record<string, string> = {
|
const o11yFamiliarityOptions: Record<string, string> = {
|
||||||
new: "I'm completely new",
|
new: "I'm completely new",
|
||||||
@ -34,10 +49,13 @@ const o11yFamiliarityOptions: Record<string, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function OrgQuestions({
|
function OrgQuestions({
|
||||||
|
isLoading,
|
||||||
orgDetails,
|
orgDetails,
|
||||||
setOrgDetails,
|
setOrgDetails,
|
||||||
onNext,
|
onNext,
|
||||||
}: OrgQuestionsProps): JSX.Element {
|
}: OrgQuestionsProps): JSX.Element {
|
||||||
|
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
const [organisationName, setOrganisationName] = useState<string>(
|
const [organisationName, setOrganisationName] = useState<string>(
|
||||||
orgDetails?.organisationName || '',
|
orgDetails?.organisationName || '',
|
||||||
);
|
);
|
||||||
@ -55,19 +73,36 @@ function OrgQuestions({
|
|||||||
);
|
);
|
||||||
const [isNextDisabled, setIsNextDisabled] = useState<boolean>(true);
|
const [isNextDisabled, setIsNextDisabled] = useState<boolean>(true);
|
||||||
|
|
||||||
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
|
useEffect(() => {
|
||||||
|
setOrganisationName(orgDetails.organisationName);
|
||||||
|
}, [orgDetails.organisationName]);
|
||||||
|
|
||||||
|
const isValidUsesObservability = (): boolean => {
|
||||||
|
if (usesObservability === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usesObservability && (!observabilityTool || observabilityTool === '')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
|
||||||
|
if (usesObservability && observabilityTool === 'Others' && otherTool === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
const isValidObservability = isValidUsesObservability();
|
||||||
organisationName !== '' &&
|
|
||||||
usesObservability !== null &&
|
if (organisationName !== '' && familiarity !== null && isValidObservability) {
|
||||||
familiarity !== null &&
|
|
||||||
(observabilityTool !== 'Others' || (usesObservability && otherTool !== ''))
|
|
||||||
) {
|
|
||||||
setIsNextDisabled(false);
|
setIsNextDisabled(false);
|
||||||
} else {
|
} else {
|
||||||
setIsNextDisabled(true);
|
setIsNextDisabled(true);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
organisationName,
|
organisationName,
|
||||||
usesObservability,
|
usesObservability,
|
||||||
@ -115,7 +150,7 @@ function OrgQuestions({
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="organisationName"
|
name="organisationName"
|
||||||
id="organisation"
|
id="organisationName"
|
||||||
placeholder="For eg. Simpsonville..."
|
placeholder="For eg. Simpsonville..."
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
value={organisationName}
|
value={organisationName}
|
||||||
@ -169,7 +204,7 @@ function OrgQuestions({
|
|||||||
Which observability tool do you currently use?
|
Which observability tool do you currently use?
|
||||||
</label>
|
</label>
|
||||||
<div className="two-column-grid">
|
<div className="two-column-grid">
|
||||||
{observabilityTools.map((tool) => (
|
{Object.keys(observabilityTools).map((tool) => (
|
||||||
<Button
|
<Button
|
||||||
key={tool}
|
key={tool}
|
||||||
type="primary"
|
type="primary"
|
||||||
@ -178,7 +213,7 @@ function OrgQuestions({
|
|||||||
}`}
|
}`}
|
||||||
onClick={(): void => setObservabilityTool(tool)}
|
onClick={(): void => setObservabilityTool(tool)}
|
||||||
>
|
>
|
||||||
{tool}
|
{observabilityTools[tool as keyof typeof observabilityTools]}
|
||||||
|
|
||||||
{observabilityTool === tool && (
|
{observabilityTool === tool && (
|
||||||
<CheckCircle size={12} color={Color.BG_FOREST_500} />
|
<CheckCircle size={12} color={Color.BG_FOREST_500} />
|
||||||
@ -191,10 +226,10 @@ function OrgQuestions({
|
|||||||
type="text"
|
type="text"
|
||||||
className="onboarding-questionaire-other-input"
|
className="onboarding-questionaire-other-input"
|
||||||
placeholder="Please specify the tool"
|
placeholder="Please specify the tool"
|
||||||
value={otherTool}
|
value={otherTool || ''}
|
||||||
autoFocus
|
autoFocus
|
||||||
addonAfter={
|
addonAfter={
|
||||||
otherTool !== '' ? (
|
otherTool && otherTool !== '' ? (
|
||||||
<CheckCircle size={12} color={Color.BG_FOREST_500} />
|
<CheckCircle size={12} color={Color.BG_FOREST_500} />
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
@ -249,7 +284,11 @@ function OrgQuestions({
|
|||||||
disabled={isNextDisabled}
|
disabled={isNextDisabled}
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
<ArrowRight size={14} />
|
{isLoading ? (
|
||||||
|
<Loader2 className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
<ArrowRight size={14} />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,30 +1,194 @@
|
|||||||
import './OnboardingQuestionaire.styles.scss';
|
import './OnboardingQuestionaire.styles.scss';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||||
|
import updateProfileAPI from 'api/onboarding/updateProfile';
|
||||||
|
import editOrg from 'api/user/editOrg';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
|
import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer';
|
||||||
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
|
import history from 'lib/history';
|
||||||
|
import { Dispatch, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useMutation } 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 AppReducer from 'types/reducer/app';
|
||||||
|
|
||||||
import { AboutSigNozQuestions } from './AboutSigNozQuestions/AboutSigNozQuestions';
|
import {
|
||||||
|
AboutSigNozQuestions,
|
||||||
|
SignozDetails,
|
||||||
|
} from './AboutSigNozQuestions/AboutSigNozQuestions';
|
||||||
import InviteTeamMembers from './InviteTeamMembers/InviteTeamMembers';
|
import InviteTeamMembers from './InviteTeamMembers/InviteTeamMembers';
|
||||||
import { OnboardingFooter } from './OnboardingFooter/OnboardingFooter';
|
import { OnboardingFooter } from './OnboardingFooter/OnboardingFooter';
|
||||||
import { OnboardingHeader } from './OnboardingHeader/OnboardingHeader';
|
import { OnboardingHeader } from './OnboardingHeader/OnboardingHeader';
|
||||||
import OptimiseSignozNeeds from './OptimiseSignozNeeds/OptimiseSignozNeeds';
|
import OptimiseSignozNeeds, {
|
||||||
import OrgQuestions from './OrgQuestions/OrgQuestions';
|
OptimiseSignozDetails,
|
||||||
|
} from './OptimiseSignozNeeds/OptimiseSignozNeeds';
|
||||||
|
import OrgQuestions, { OrgData, OrgDetails } from './OrgQuestions/OrgQuestions';
|
||||||
|
|
||||||
|
export const showErrorNotification = (
|
||||||
|
notifications: NotificationInstance,
|
||||||
|
err: Error,
|
||||||
|
): void => {
|
||||||
|
notifications.error({
|
||||||
|
message: err.message || SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const INITIAL_ORG_DETAILS: OrgDetails = {
|
||||||
|
organisationName: '',
|
||||||
|
usesObservability: true,
|
||||||
|
observabilityTool: '',
|
||||||
|
otherTool: '',
|
||||||
|
familiarity: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const INITIAL_SIGNOZ_DETAILS: SignozDetails = {
|
||||||
|
hearAboutSignoz: '',
|
||||||
|
interestInSignoz: '',
|
||||||
|
otherInterestInSignoz: '',
|
||||||
|
otherAboutSignoz: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = {
|
||||||
|
logsPerDay: 25,
|
||||||
|
hostsPerDay: 40,
|
||||||
|
services: 10,
|
||||||
|
};
|
||||||
|
|
||||||
function OnboardingQuestionaire(): JSX.Element {
|
function OnboardingQuestionaire(): JSX.Element {
|
||||||
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
const [currentStep, setCurrentStep] = useState<number>(1);
|
const [currentStep, setCurrentStep] = useState<number>(1);
|
||||||
|
const [orgDetails, setOrgDetails] = useState<OrgDetails>(INITIAL_ORG_DETAILS);
|
||||||
const [orgDetails, setOrgDetails] = useState<Record<string, string> | null>(
|
const [signozDetails, setSignozDetails] = useState<SignozDetails>(
|
||||||
null,
|
INITIAL_SIGNOZ_DETAILS,
|
||||||
);
|
);
|
||||||
const [signozDetails, setSignozDetails] = useState<Record<
|
const [
|
||||||
string,
|
optimiseSignozDetails,
|
||||||
string
|
setOptimiseSignozDetails,
|
||||||
> | null>(null);
|
] = useState<OptimiseSignozDetails>(INITIAL_OPTIMISE_SIGNOZ_DETAILS);
|
||||||
const [optimiseSignozDetails, setOptimiseSignozDetails] = useState<Record<
|
const [teamMembers, setTeamMembers] = useState<
|
||||||
string,
|
InviteTeamMembersProps[] | null
|
||||||
number
|
>(null);
|
||||||
> | null>(null);
|
|
||||||
|
|
||||||
const [teamMembers, setTeamMembers] = useState<string[]>(['']);
|
const { t } = useTranslation(['organizationsettings', 'common']);
|
||||||
|
const { org } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
|
const [orgData, setOrgData] = useState<OrgData | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (org) {
|
||||||
|
setOrgData(org[0]);
|
||||||
|
|
||||||
|
setOrgDetails({
|
||||||
|
...orgDetails,
|
||||||
|
organisationName: org[0].name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [org]);
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setCurrentStep(2);
|
||||||
|
} else {
|
||||||
|
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,
|
||||||
|
{
|
||||||
|
onSuccess: (data) => {
|
||||||
|
console.log('data', data);
|
||||||
|
|
||||||
|
setCurrentStep(4);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
showErrorNotification(notifications, error as AxiosError);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleUpdateProfile = (): void => {
|
||||||
|
updateProfile({
|
||||||
|
familiarity_with_observability: orgDetails?.familiarity as string,
|
||||||
|
has_existing_observability_tool: orgDetails?.usesObservability as boolean,
|
||||||
|
existing_observability_tool:
|
||||||
|
orgDetails?.observabilityTool === 'Others'
|
||||||
|
? (orgDetails?.otherTool as string)
|
||||||
|
: (orgDetails?.observabilityTool as string),
|
||||||
|
|
||||||
|
reasons_for_interest_in_signoz:
|
||||||
|
signozDetails?.interestInSignoz === 'Others'
|
||||||
|
? (signozDetails?.otherInterestInSignoz as string)
|
||||||
|
: (signozDetails?.interestInSignoz as string),
|
||||||
|
where_did_you_hear_about_signoz:
|
||||||
|
signozDetails?.hearAboutSignoz === 'Others'
|
||||||
|
? (signozDetails?.otherAboutSignoz as string)
|
||||||
|
: (signozDetails?.hearAboutSignoz as string),
|
||||||
|
|
||||||
|
logs_scale_per_day_in_gb: optimiseSignozDetails?.logsPerDay as number,
|
||||||
|
number_of_hosts: optimiseSignozDetails?.hostsPerDay as number,
|
||||||
|
number_of_services: optimiseSignozDetails?.services as number,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnboardingComplete = (): void => {
|
||||||
|
history.push('/');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="onboarding-questionaire-container">
|
<div className="onboarding-questionaire-container">
|
||||||
@ -35,9 +199,10 @@ function OnboardingQuestionaire(): JSX.Element {
|
|||||||
<div className="onboarding-questionaire-content">
|
<div className="onboarding-questionaire-content">
|
||||||
{currentStep === 1 && (
|
{currentStep === 1 && (
|
||||||
<OrgQuestions
|
<OrgQuestions
|
||||||
|
isLoading={isLoading}
|
||||||
orgDetails={orgDetails}
|
orgDetails={orgDetails}
|
||||||
setOrgDetails={setOrgDetails}
|
setOrgDetails={setOrgDetails}
|
||||||
onNext={(): void => setCurrentStep(2)}
|
onNext={handleOrgDetailsUpdate}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -52,10 +217,11 @@ function OnboardingQuestionaire(): JSX.Element {
|
|||||||
|
|
||||||
{currentStep === 3 && (
|
{currentStep === 3 && (
|
||||||
<OptimiseSignozNeeds
|
<OptimiseSignozNeeds
|
||||||
|
isUpdatingProfile={isUpdatingProfile}
|
||||||
optimiseSignozDetails={optimiseSignozDetails}
|
optimiseSignozDetails={optimiseSignozDetails}
|
||||||
setOptimiseSignozDetails={setOptimiseSignozDetails}
|
setOptimiseSignozDetails={setOptimiseSignozDetails}
|
||||||
onBack={(): void => setCurrentStep(2)}
|
onBack={(): void => setCurrentStep(2)}
|
||||||
onNext={(): void => setCurrentStep(4)}
|
onNext={handleUpdateProfile}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -64,7 +230,7 @@ function OnboardingQuestionaire(): JSX.Element {
|
|||||||
teamMembers={teamMembers}
|
teamMembers={teamMembers}
|
||||||
setTeamMembers={setTeamMembers}
|
setTeamMembers={setTeamMembers}
|
||||||
onBack={(): void => setCurrentStep(3)}
|
onBack={(): void => setCurrentStep(3)}
|
||||||
onNext={(): void => setCurrentStep(5)}
|
onNext={handleOnboardingComplete}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -236,7 +236,9 @@ function PendingInvitesContainer(): JSX.Element {
|
|||||||
export interface InviteTeamMembersProps {
|
export interface InviteTeamMembersProps {
|
||||||
email: string;
|
email: string;
|
||||||
name: string;
|
name: string;
|
||||||
role: ROLES;
|
role: string;
|
||||||
|
id: string;
|
||||||
|
frontendBaseUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DataProps {
|
interface DataProps {
|
||||||
|
10
frontend/src/types/api/onboarding/types.ts
Normal file
10
frontend/src/types/api/onboarding/types.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export interface UpdateProfileProps {
|
||||||
|
reasons_for_interest_in_signoz: string;
|
||||||
|
familiarity_with_observability: string;
|
||||||
|
has_existing_observability_tool: boolean;
|
||||||
|
existing_observability_tool: string;
|
||||||
|
logs_scale_per_day_in_gb: number;
|
||||||
|
number_of_services: number;
|
||||||
|
number_of_hosts: number;
|
||||||
|
where_did_you_hear_about_signoz: string;
|
||||||
|
}
|
17
frontend/src/types/api/user/inviteUsers.ts
Normal file
17
frontend/src/types/api/user/inviteUsers.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { User } from 'types/reducer/app';
|
||||||
|
import { ROLES } from 'types/roles';
|
||||||
|
|
||||||
|
export interface UserProps {
|
||||||
|
name: User['name'];
|
||||||
|
email: User['email'];
|
||||||
|
role: ROLES;
|
||||||
|
frontendBaseUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UsersProps {
|
||||||
|
users: UserProps[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PayloadProps {
|
||||||
|
data: string;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user