From 6c350f30aa929932cc9382405ba42c28dd596e46 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Wed, 23 Oct 2024 19:07:37 +0530 Subject: [PATCH] feat: integrate update profile and invite users api --- frontend/src/api/apiV1.ts | 1 + frontend/src/api/index.ts | 14 ++ frontend/src/api/onboarding/updateProfile.ts | 26 +++ frontend/src/api/user/inviteUsers.ts | 25 +++ .../AboutSigNozQuestions.tsx | 64 +++--- .../InviteTeamMembers.styles.scss | 46 ++++ .../InviteTeamMembers/InviteTeamMembers.tsx | 170 +++++++++++---- .../OptimiseSignozNeeds.tsx | 57 +++-- .../OrgQuestions/OrgQuestions.tsx | 89 +++++--- .../OnboardingQuestionaire/index.tsx | 204 ++++++++++++++++-- .../PendingInvitesContainer/index.tsx | 4 +- frontend/src/types/api/onboarding/types.ts | 10 + frontend/src/types/api/user/inviteUsers.ts | 17 ++ 13 files changed, 595 insertions(+), 132 deletions(-) create mode 100644 frontend/src/api/onboarding/updateProfile.ts create mode 100644 frontend/src/api/user/inviteUsers.ts create mode 100644 frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss create mode 100644 frontend/src/types/api/onboarding/types.ts create mode 100644 frontend/src/types/api/user/inviteUsers.ts diff --git a/frontend/src/api/apiV1.ts b/frontend/src/api/apiV1.ts index 613ed27a17..abd7d701a4 100644 --- a/frontend/src/api/apiV1.ts +++ b/frontend/src/api/apiV1.ts @@ -4,6 +4,7 @@ export const apiV2 = '/api/v2/'; export const apiV3 = '/api/v3/'; export const apiV4 = '/api/v4/'; export const gatewayApiV1 = '/api/gateway/v1/'; +export const gatewayApiV2 = '/api/gateway/v2/'; export const apiAlertManager = '/api/alertmanager/'; export default apiV1; diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 7f5e2d476c..a340f03ff1 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -15,6 +15,7 @@ import apiV1, { apiV3, apiV4, gatewayApiV1, + gatewayApiV2, } from './apiV1'; import { Logout } from './utils'; @@ -169,6 +170,19 @@ GatewayApiV1Instance.interceptors.response.use( 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( interceptorsResponse, interceptorRejected, diff --git a/frontend/src/api/onboarding/updateProfile.ts b/frontend/src/api/onboarding/updateProfile.ts new file mode 100644 index 0000000000..f7f5ffe22c --- /dev/null +++ b/frontend/src/api/onboarding/updateProfile.ts @@ -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 | 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; diff --git a/frontend/src/api/user/inviteUsers.ts b/frontend/src/api/user/inviteUsers.ts new file mode 100644 index 0000000000..6722b26593 --- /dev/null +++ b/frontend/src/api/user/inviteUsers.ts @@ -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 | 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; diff --git a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx index c573d552fe..df8066b358 100644 --- a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx @@ -7,9 +7,16 @@ import logEvent from 'api/common/logEvent'; import { ArrowLeft, ArrowRight, CheckCircle } from 'lucide-react'; import { useEffect, useState } from 'react'; +export interface SignozDetails { + hearAboutSignoz: string | null; + interestInSignoz: string | null; + otherInterestInSignoz: string | null; + otherAboutSignoz: string | null; +} + interface AboutSigNozQuestionsProps { - signozDetails: any; - setSignozDetails: (details: any) => void; + signozDetails: SignozDetails; + setSignozDetails: (details: SignozDetails) => void; onNext: () => void; onBack: () => void; } @@ -41,11 +48,11 @@ export function AboutSigNozQuestions({ const [otherAboutSignoz, setOtherAboutSignoz] = useState( signozDetails?.otherAboutSignoz || '', ); - const [interestedSignoz, setInterestedSignoz] = useState( - signozDetails?.interestedSignoz || null, + const [interestInSignoz, setInterestInSignoz] = useState( + signozDetails?.interestInSignoz || null, ); - const [otherInterest, setOtherInterest] = useState( - signozDetails?.otherInterest || '', + const [otherInterestInSignoz, setOtherInterestInSignoz] = useState( + signozDetails?.otherInterestInSignoz || '', ); const [isNextDisabled, setIsNextDisabled] = useState(true); @@ -53,28 +60,33 @@ export function AboutSigNozQuestions({ if ( hearAboutSignoz !== null && (hearAboutSignoz !== 'Others' || otherAboutSignoz !== '') && - interestedSignoz !== null && - (interestedSignoz !== 'Others' || otherInterest !== '') + interestInSignoz !== null && + (interestInSignoz !== 'Others' || otherInterestInSignoz !== '') ) { setIsNextDisabled(false); } else { setIsNextDisabled(true); } - }, [hearAboutSignoz, otherAboutSignoz, interestedSignoz, otherInterest]); + }, [ + hearAboutSignoz, + otherAboutSignoz, + interestInSignoz, + otherInterestInSignoz, + ]); const handleOnNext = (): void => { setSignozDetails({ hearAboutSignoz, otherAboutSignoz, - interestedSignoz, - otherInterest, + interestInSignoz, + otherInterestInSignoz, }); logEvent('Onboarding: SigNoz Questions: Next', { hearAboutSignoz, otherAboutSignoz, - interestedSignoz, - otherInterest, + interestInSignoz, + otherInterestInSignoz, }); onNext(); @@ -84,15 +96,15 @@ export function AboutSigNozQuestions({ setSignozDetails({ hearAboutSignoz, otherAboutSignoz, - interestedSignoz, - otherInterest, + interestInSignoz, + otherInterestInSignoz, }); logEvent('Onboarding: SigNoz Questions: Back', { hearAboutSignoz, otherAboutSignoz, - interestedSignoz, - otherInterest, + interestInSignoz, + otherInterestInSignoz, }); onBack(); @@ -168,40 +180,40 @@ export function AboutSigNozQuestions({ key={option} type="primary" className={`onboarding-questionaire-button ${ - interestedSignoz === option ? 'active' : '' + interestInSignoz === option ? 'active' : '' }`} - onClick={(): void => setInterestedSignoz(option)} + onClick={(): void => setInterestInSignoz(option)} > {interestedInOptions[option]} - {interestedSignoz === option && ( + {interestInSignoz === option && ( )} ))} - {interestedSignoz === 'Others' ? ( + {interestInSignoz === 'Others' ? ( ) : ( '' ) } - onChange={(e): void => setOtherInterest(e.target.value)} + onChange={(e): void => setOtherInterestInSignoz(e.target.value)} /> ) : ( diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss new file mode 100644 index 0000000000..aae10276f6 --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss @@ -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; + } + } +} diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx index 6eeb2c5e6f..cd5f0d53c4 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx @@ -1,5 +1,8 @@ +import './InviteTeamMembers.styles.scss'; + import { Color } from '@signozhq/design-tokens'; import { Button, Input, Select, Typography } from 'antd'; +import { cloneDeep, debounce, isEmpty } from 'lodash-es'; import { ArrowLeft, ArrowRight, @@ -7,55 +10,123 @@ import { Plus, TriangleAlert, } 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 { - teamMembers: string[]; - setTeamMembers: (teamMembers: string[]) => void; + teamMembers: TeamMember[] | null; + setTeamMembers: (teamMembers: TeamMember[]) => void; onNext: () => void; onBack: () => void; } -const userRolesOptions = ( - -); - function InviteTeamMembers({ teamMembers, setTeamMembers, onNext, onBack, }: InviteTeamMembersProps): JSX.Element { - const [teamMembersToInvite, setTeamMembersToInvite] = useState( - teamMembers || [''], + const [teamMembersToInvite, setTeamMembersToInvite] = useState< + TeamMember[] | null + >(teamMembers); + + const [emailValidity, setEmailValidity] = useState>( + {}, ); + const [hasInvalidEmails, setHasInvalidEmails] = useState(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 => { - 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 = {}; + + 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 => { - console.log(teamMembersToInvite); - setTeamMembers(teamMembersToInvite); - onNext(); + if (validateAllUsers()) { + setTeamMembers(teamMembersToInvite || []); + 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, - index: number, + member: TeamMember, ): void => { - const newTeamMembers = [...teamMembersToInvite]; - newTeamMembers[index] = e.target.value; - setTeamMembersToInvite(newTeamMembers); + const { value } = e.target; + const updatedMembers = cloneDeep(teamMembersToInvite || []); + + 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 emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); + const handleRoleChange = (role: string, member: TeamMember): void => { + const updatedMembers = cloneDeep(teamMembersToInvite || []); + const memberToUpdate = updatedMembers.find((m) => m.id === member.id); + if (memberToUpdate) { + memberToUpdate.role = role; + setTeamMembersToInvite(updatedMembers); + } }; return ( @@ -79,29 +150,37 @@ function InviteTeamMembers({
- {teamMembersToInvite.map((member, index) => ( - // eslint-disable-next-line react/no-array-index-key -
+ {teamMembersToInvite?.map((member) => ( +
+ 0 ? ( - isValidEmail(member) ? ( - - ) : ( - - ) - ) : null - } placeholder="your-teammate@org.com" - value={member} + value={member.email} type="email" required autoFocus autoComplete="off" + className="team-member-email-input" onChange={(e: React.ChangeEvent): void => - handleOnChange(e, index) + handleEmailChange(e, member) + } + addonAfter={ + // eslint-disable-next-line no-nested-ternary + emailValidity[member.id!] === undefined ? null : emailValidity[ + member.id! + ] ? ( + + ) : ( + + ) } />
@@ -121,6 +200,15 @@ function InviteTeamMembers({
+ {hasInvalidEmails && ( +
+ + Please enter valid emails for all team + members + +
+ )} +
- -
diff --git a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx index 6cd679c5da..a1fea0aaf8 100644 --- a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx @@ -4,27 +4,42 @@ import '../OnboardingQuestionaire.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Input, Typography } from 'antd'; 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 { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; 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 { - orgDetails: any; - setOrgDetails: (details: any) => void; + isLoading: boolean; + orgDetails: OrgDetails; + setOrgDetails: (details: OrgDetails) => void; onNext: () => void; } -const observabilityTools = [ - 'AWS Cloudwatch', - 'DataDog', - 'New Relic', - 'Grafana / Prometheus', - 'Azure App Monitor', - 'GCP-native o11y tools', - 'Honeycomb', -]; +const observabilityTools = { + AWSCloudwatch: 'AWS Cloudwatch', + DataDog: 'DataDog', + NewRelic: 'New Relic', + GrafanaPrometheus: 'Grafana / Prometheus', + AzureAppMonitor: 'Azure App Monitor', + GCPNativeO11yTools: 'GCP-native o11y tools', + Honeycomb: 'Honeycomb', +}; const o11yFamiliarityOptions: Record = { new: "I'm completely new", @@ -34,10 +49,13 @@ const o11yFamiliarityOptions: Record = { }; function OrgQuestions({ + isLoading, orgDetails, setOrgDetails, onNext, }: OrgQuestionsProps): JSX.Element { + const { user } = useSelector((state) => state.app); + const [organisationName, setOrganisationName] = useState( orgDetails?.organisationName || '', ); @@ -55,19 +73,36 @@ function OrgQuestions({ ); const [isNextDisabled, setIsNextDisabled] = useState(true); - const { user } = useSelector((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(() => { - if ( - organisationName !== '' && - usesObservability !== null && - familiarity !== null && - (observabilityTool !== 'Others' || (usesObservability && otherTool !== '')) - ) { + const isValidObservability = isValidUsesObservability(); + + if (organisationName !== '' && familiarity !== null && isValidObservability) { setIsNextDisabled(false); } else { setIsNextDisabled(true); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ organisationName, usesObservability, @@ -115,7 +150,7 @@ function OrgQuestions({
- {observabilityTools.map((tool) => ( + {Object.keys(observabilityTools).map((tool) => (
diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index bb9e88d0d4..39e34e1f82 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -1,30 +1,194 @@ 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 { OnboardingFooter } from './OnboardingFooter/OnboardingFooter'; import { OnboardingHeader } from './OnboardingHeader/OnboardingHeader'; -import OptimiseSignozNeeds from './OptimiseSignozNeeds/OptimiseSignozNeeds'; -import OrgQuestions from './OrgQuestions/OrgQuestions'; +import OptimiseSignozNeeds, { + 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 { + const { notifications } = useNotifications(); + const [currentStep, setCurrentStep] = useState(1); - - const [orgDetails, setOrgDetails] = useState | null>( - null, + const [orgDetails, setOrgDetails] = useState(INITIAL_ORG_DETAILS); + const [signozDetails, setSignozDetails] = useState( + INITIAL_SIGNOZ_DETAILS, ); - const [signozDetails, setSignozDetails] = useState | null>(null); - const [optimiseSignozDetails, setOptimiseSignozDetails] = useState | null>(null); + const [ + optimiseSignozDetails, + setOptimiseSignozDetails, + ] = useState(INITIAL_OPTIMISE_SIGNOZ_DETAILS); + const [teamMembers, setTeamMembers] = useState< + InviteTeamMembersProps[] | null + >(null); - const [teamMembers, setTeamMembers] = useState(['']); + const { t } = useTranslation(['organizationsettings', 'common']); + const { org } = useSelector((state) => state.app); + const dispatch = useDispatch>(); + const [orgData, setOrgData] = useState(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(false); + + const handleOrgNameUpdate = async (): Promise => { + /* 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 (
@@ -35,9 +199,10 @@ function OnboardingQuestionaire(): JSX.Element {
{currentStep === 1 && ( setCurrentStep(2)} + onNext={handleOrgDetailsUpdate} /> )} @@ -52,10 +217,11 @@ function OnboardingQuestionaire(): JSX.Element { {currentStep === 3 && ( setCurrentStep(2)} - onNext={(): void => setCurrentStep(4)} + onNext={handleUpdateProfile} /> )} @@ -64,7 +230,7 @@ function OnboardingQuestionaire(): JSX.Element { teamMembers={teamMembers} setTeamMembers={setTeamMembers} onBack={(): void => setCurrentStep(3)} - onNext={(): void => setCurrentStep(5)} + onNext={handleOnboardingComplete} /> )}
diff --git a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx index 7c4909ab8d..07c1d8f219 100644 --- a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx +++ b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx @@ -236,7 +236,9 @@ function PendingInvitesContainer(): JSX.Element { export interface InviteTeamMembersProps { email: string; name: string; - role: ROLES; + role: string; + id: string; + frontendBaseUrl: string; } interface DataProps { diff --git a/frontend/src/types/api/onboarding/types.ts b/frontend/src/types/api/onboarding/types.ts new file mode 100644 index 0000000000..046b7df18d --- /dev/null +++ b/frontend/src/types/api/onboarding/types.ts @@ -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; +} diff --git a/frontend/src/types/api/user/inviteUsers.ts b/frontend/src/types/api/user/inviteUsers.ts new file mode 100644 index 0000000000..cf12269e73 --- /dev/null +++ b/frontend/src/types/api/user/inviteUsers.ts @@ -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; +}