mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 08:49:02 +08:00
chore(auth): refactor the client handlers in preparation for multi tenant login (#7902)
* chore: update auth * chore: password changes * chore: make changes in oss code * chore: login * chore: get to a running state * fix: migration inital commit * fix: signoz cloud intgtn tests * fix: minor fixes * chore: sso code fixed with org domain * fix: tests * fix: ee auth api's * fix: changes in name * fix: return user in login api * fix: address comments * fix: validate password * fix: handle get domain by email properly * fix: move authomain to usermodule * fix: use displayname instead of hname * fix: rename back endpoints * fix: update telemetry * fix: correct errors * fix: test and fix the invite endpoints * fix: delete all things related to user in store * fix: address issues * fix: ee delete invite * fix: rename func * fix: update user and update role * fix: update role * chore(api): update the api folder structure according to rest principles * fix: login and invite changes * chore(api): update the api folder structure according to rest principles * chore(login): update the frontend according to the new APIs * fix: return org name in users response * chore(login): update the frontend according to the new APIs * fix: update user role * fix: nil check * chore(login): update the frontend according to the new API * fix: getinvite and update role * fix: sso * fix: getinvite use sso ctx * fix: use correct sourceurl * fix: getsourceurl from req payload * chore(login): update the frontend according to the new API * fix: update created_at * fix: fix reset password * chore(login): fixed reset password and bulk invites * fix: sso signup and token password change * fix: don't delete last admin * fix: reset password and migration * fix: migration * chore(login): fix the unwanted throw statement and tsconfig * fix: reset password for sso users * fix: clean up invite * chore(login): delete last admin user and reset password * fix: migration * fix: update claims and store code * fix: use correct error * fix: proper nil checks * fix: make migration multitenant * fix: address comments * fix: minor fixes * fix: test * fix: rename reset password * fix: set self restration only when sso endabled * chore(auth): update the invite user API * fix: integration tests * fix: integration tests * fix: integration tests * fix: integration tests * fix: integration tests * fix: integration tests * fix: integration tests * chore(auth): update integration test * fix: telemetry --------- Co-authored-by: nityanandagohain <nityanandagohain@gmail.com>
This commit is contained in:
parent
0a2b7ca1d8
commit
f525647b40
@ -110,6 +110,8 @@ module.exports = {
|
||||
// eslint rules need to remove
|
||||
'@typescript-eslint/no-shadow': 'off',
|
||||
'import/no-cycle': 'off',
|
||||
// https://typescript-eslint.io/rules/consistent-return/ check the warning for details
|
||||
'consistent-return': 'off',
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import getOrgUser from 'api/v1/user/getOrgUser';
|
||||
import getAll from 'api/v1/user/get';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -11,8 +11,11 @@ import { useAppContext } from 'providers/App/App';
|
||||
import { ReactChild, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
|
||||
import { Organization } from 'types/api/user/getOrganization';
|
||||
import { UserResponse } from 'types/api/user/getUser';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { routePermission } from 'utils/permission';
|
||||
|
||||
@ -58,12 +61,13 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
|
||||
const [orgData, setOrgData] = useState<Organization | undefined>(undefined);
|
||||
|
||||
const { data: orgUsers, isFetching: isFetchingOrgUsers } = useQuery({
|
||||
const { data: usersData, isFetching: isFetchingUsers } = useQuery<
|
||||
SuccessResponseV2<UserResponse[]> | undefined,
|
||||
APIError
|
||||
>({
|
||||
queryFn: () => {
|
||||
if (orgData && orgData.id !== undefined) {
|
||||
return getOrgUser({
|
||||
orgId: orgData.id,
|
||||
});
|
||||
return getAll();
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
@ -72,23 +76,23 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
});
|
||||
|
||||
const checkFirstTimeUser = useCallback((): boolean => {
|
||||
const users = orgUsers?.payload || [];
|
||||
const users = usersData?.data || [];
|
||||
|
||||
const remainingUsers = users.filter(
|
||||
(user) => user.email !== 'admin@signoz.cloud',
|
||||
);
|
||||
|
||||
return remainingUsers.length === 1;
|
||||
}, [orgUsers?.payload]);
|
||||
}, [usersData?.data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
isCloudUserVal &&
|
||||
!isFetchingOrgPreferences &&
|
||||
orgPreferences &&
|
||||
!isFetchingOrgUsers &&
|
||||
orgUsers &&
|
||||
orgUsers.payload
|
||||
!isFetchingUsers &&
|
||||
usersData &&
|
||||
usersData.data
|
||||
) {
|
||||
const isOnboardingComplete = orgPreferences?.find(
|
||||
(preference: Record<string, any>) => preference.key === 'ORG_ONBOARDING',
|
||||
@ -108,9 +112,9 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
checkFirstTimeUser,
|
||||
isCloudUserVal,
|
||||
isFetchingOrgPreferences,
|
||||
isFetchingOrgUsers,
|
||||
isFetchingUsers,
|
||||
orgPreferences,
|
||||
orgUsers,
|
||||
usersData,
|
||||
pathname,
|
||||
]);
|
||||
|
||||
|
@ -70,14 +70,14 @@ function App(): JSX.Element {
|
||||
const orgName =
|
||||
org && Array.isArray(org) && org.length > 0 ? org[0].displayName : '';
|
||||
|
||||
const { name, email, role } = user;
|
||||
const { displayName, email, role } = user;
|
||||
|
||||
const domain = extractDomain(email);
|
||||
const hostNameParts = hostname.split('.');
|
||||
|
||||
const identifyPayload = {
|
||||
email,
|
||||
name,
|
||||
name: displayName,
|
||||
company_name: orgName,
|
||||
tenant_id: hostNameParts[0],
|
||||
data_region: hostNameParts[1],
|
||||
@ -106,7 +106,7 @@ function App(): JSX.Element {
|
||||
|
||||
Userpilot.identify(email, {
|
||||
email,
|
||||
name,
|
||||
name: displayName,
|
||||
orgName,
|
||||
tenant_id: hostNameParts[0],
|
||||
data_region: hostNameParts[1],
|
||||
@ -118,7 +118,7 @@ function App(): JSX.Element {
|
||||
|
||||
posthog?.identify(email, {
|
||||
email,
|
||||
name,
|
||||
name: displayName,
|
||||
orgName,
|
||||
tenant_id: hostNameParts[0],
|
||||
data_region: hostNameParts[1],
|
||||
@ -144,7 +144,7 @@ function App(): JSX.Element {
|
||||
) {
|
||||
window.cioanalytics.reset();
|
||||
window.cioanalytics.identify(email, {
|
||||
name: user.name,
|
||||
name: user.displayName,
|
||||
email,
|
||||
role: user.role,
|
||||
});
|
||||
@ -258,7 +258,7 @@ function App(): JSX.Element {
|
||||
window.Intercom('boot', {
|
||||
app_id: process.env.INTERCOM_APP_ID,
|
||||
email: user?.email || '',
|
||||
name: user?.name || '',
|
||||
name: user?.displayName || '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import loginApi from 'api/v1/user/login';
|
||||
import loginApi from 'api/v1/login/login';
|
||||
import afterLogin from 'AppRoutes/utils';
|
||||
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
@ -71,15 +71,15 @@ const interceptorRejected = async (
|
||||
const { response } = value;
|
||||
// reject the refresh token error
|
||||
if (response.status === 401 && response.config.url !== '/login') {
|
||||
const response = await loginApi({
|
||||
refreshToken: getLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN) || '',
|
||||
});
|
||||
try {
|
||||
const response = await loginApi({
|
||||
refreshToken: getLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN) || '',
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
afterLogin(
|
||||
response.payload.userId,
|
||||
response.payload.accessJwt,
|
||||
response.payload.refreshJwt,
|
||||
response.data.userId,
|
||||
response.data.accessJwt,
|
||||
response.data.refreshJwt,
|
||||
true,
|
||||
);
|
||||
|
||||
@ -89,23 +89,22 @@ const interceptorRejected = async (
|
||||
method: value.config.method,
|
||||
headers: {
|
||||
...value.config.headers,
|
||||
Authorization: `Bearer ${response.payload.accessJwt}`,
|
||||
Authorization: `Bearer ${response.data.accessJwt}`,
|
||||
},
|
||||
data: {
|
||||
...JSON.parse(value.config.data || '{}'),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (reResponse.status === 200) {
|
||||
return await Promise.resolve(reResponse);
|
||||
}
|
||||
Logout();
|
||||
return await Promise.reject(reResponse);
|
||||
} catch (error) {
|
||||
Logout();
|
||||
}
|
||||
Logout();
|
||||
}
|
||||
|
||||
// when refresh token is expired
|
||||
if (response.status === 401 && response.config.url === '/login') {
|
||||
Logout();
|
||||
|
@ -1,25 +1,26 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/changeMyPassword';
|
||||
|
||||
const changeMyPassword = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post(`/changePassword/${props.userId}`, {
|
||||
...props,
|
||||
});
|
||||
const response = await axios.post<PayloadProps>(
|
||||
`/changePassword/${props.userId}`,
|
||||
{
|
||||
...props,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,23 +1,27 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/getResetPasswordToken';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import {
|
||||
GetResetPasswordToken,
|
||||
PayloadProps,
|
||||
Props,
|
||||
} from 'types/api/user/getResetPasswordToken';
|
||||
|
||||
const getResetPasswordToken = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
): Promise<SuccessResponseV2<GetResetPasswordToken>> => {
|
||||
try {
|
||||
const response = await axios.get(`/getResetPasswordToken/${props.userId}`);
|
||||
const response = await axios.get<PayloadProps>(
|
||||
`/getResetPasswordToken/${props.userId}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,25 +1,23 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/resetPassword';
|
||||
|
||||
const resetPassword = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post(`/resetPassword`, {
|
||||
const response = await axios.post<PayloadProps>(`/resetPassword`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.statusText,
|
||||
payload: response.data,
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,18 +1,21 @@
|
||||
import axios from 'api';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { InviteUsersResponse, UsersProps } from 'types/api/user/inviteUsers';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { UsersProps } from 'types/api/user/inviteUsers';
|
||||
|
||||
const inviteUsers = async (
|
||||
users: UsersProps,
|
||||
): Promise<SuccessResponse<InviteUsersResponse>> => {
|
||||
const response = await axios.post(`/invite/bulk`, users);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.post(`/invite/bulk`, users);
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: null,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default inviteUsers;
|
||||
|
@ -1,25 +1,23 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/setInvite';
|
||||
|
||||
const sendInvite = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post(`/invite`, {
|
||||
const response = await axios.post<PayloadProps>(`/invite`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
|
19
frontend/src/api/v1/invite/get.ts
Normal file
19
frontend/src/api/v1/invite/get.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, PendingInvite } from 'types/api/user/getPendingInvites';
|
||||
|
||||
const get = async (): Promise<SuccessResponseV2<PendingInvite[]>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/invite`);
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default get;
|
@ -1,24 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/user/getPendingInvites';
|
||||
|
||||
const getPendingInvites = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(`/invite`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getPendingInvites;
|
25
frontend/src/api/v1/invite/id/accept.ts
Normal file
25
frontend/src/api/v1/invite/id/accept.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import {
|
||||
LoginPrecheckResponse,
|
||||
PayloadProps,
|
||||
Props,
|
||||
} from 'types/api/user/accept';
|
||||
|
||||
const accept = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<LoginPrecheckResponse>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>(`/invite/accept`, props);
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default accept;
|
@ -1,24 +1,20 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/deleteInvite';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { Props } from 'types/api/user/deleteInvite';
|
||||
|
||||
const deleteInvite = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const del = async (props: Props): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.delete(`/invite/${props.email}`);
|
||||
const response = await axios.delete(`/invite/${props.id}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
httpStatusCode: response.status,
|
||||
data: null,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default deleteInvite;
|
||||
export default del;
|
||||
|
@ -1,25 +1,27 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/getInviteDetails';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import {
|
||||
InviteDetails,
|
||||
PayloadProps,
|
||||
Props,
|
||||
} from 'types/api/user/getInviteDetails';
|
||||
|
||||
const getInviteDetails = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
): Promise<SuccessResponseV2<InviteDetails>> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
const response = await axios.get<PayloadProps>(
|
||||
`/invite/${props.inviteId}?ref=${window.location.href}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,11 +2,11 @@ import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/login';
|
||||
import { PayloadProps, Props, UserLoginResponse } from 'types/api/user/login';
|
||||
|
||||
const login = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
): Promise<SuccessResponseV2<UserLoginResponse>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>(`/login`, {
|
||||
...props,
|
||||
@ -14,12 +14,10 @@ const login = async (
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
// this line is never reached but ts isn't detecting the never type properly for the ErrorResponseHandlerV2
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
21
frontend/src/api/v1/user/get.ts
Normal file
21
frontend/src/api/v1/user/get.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { UserResponse } from 'types/api/user/getUser';
|
||||
import { PayloadProps } from 'types/api/user/getUsers';
|
||||
|
||||
const getAll = async (): Promise<SuccessResponseV2<UserResponse[]>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/user`);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default getAll;
|
@ -1,24 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/getOrgMembers';
|
||||
|
||||
const getOrgUser = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/orgUsers/${props.orgId}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getOrgUser;
|
@ -1,23 +1,19 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/deleteUser';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { Props } from 'types/api/user/deleteUser';
|
||||
|
||||
const deleteUser = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const deleteUser = async (props: Props): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.delete(`/user/${props.userId}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
httpStatusCode: response.status,
|
||||
data: null,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,18 +1,22 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/getUser';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props, UserResponse } from 'types/api/user/getUser';
|
||||
|
||||
const getUser = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.get(`/user/${props.userId}`);
|
||||
): Promise<SuccessResponseV2<UserResponse>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/user/${props.userId}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default getUser;
|
||||
|
@ -1,28 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/getUserRole';
|
||||
|
||||
const getRoles = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/rbac/role/${props.userId}`, {
|
||||
headers: {
|
||||
Authorization: `bearer ${props.token}`,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getRoles;
|
@ -1,26 +1,23 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/editUser';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { Props } from 'types/api/user/editUser';
|
||||
|
||||
const editUser = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const update = async (props: Props): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.put(`/user/${props.userId}`, {
|
||||
Name: props.name,
|
||||
displayName: props.displayName,
|
||||
role: props.role,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
httpStatusCode: response.status,
|
||||
data: null,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default editUser;
|
||||
export default update;
|
||||
|
@ -1,26 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/updateRole';
|
||||
|
||||
const updateRole = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.put(`/rbac/role/${props.userId}`, {
|
||||
group_name: props.group_name,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default updateRole;
|
@ -1,26 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/login';
|
||||
|
||||
const login = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`/login`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.statusText,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default login;
|
@ -1,8 +1,8 @@
|
||||
import { Button, Form, Input, Space, Tooltip, Typography } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import loginApi from 'api/v1/login/login';
|
||||
import loginPrecheckApi from 'api/v1/login/loginPrecheck';
|
||||
import loginApi from 'api/v1/user/login';
|
||||
import getUserVersion from 'api/v1/version/getVersion';
|
||||
import afterLogin from 'AppRoutes/utils';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
@ -13,6 +13,7 @@ import { useAppContext } from 'providers/App/App';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import APIError from 'types/api/error';
|
||||
import { PayloadProps as PrecheckResultType } from 'types/api/user/loginPrecheck';
|
||||
|
||||
import { FormContainer, FormWrapper, Label, ParentContainer } from './styles';
|
||||
@ -166,22 +167,18 @@ function Login({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
if (response.statusCode === 200) {
|
||||
afterLogin(
|
||||
response.payload.userId,
|
||||
response.payload.accessJwt,
|
||||
response.payload.refreshJwt,
|
||||
);
|
||||
} else {
|
||||
notifications.error({
|
||||
message: response.error || t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
|
||||
afterLogin(
|
||||
response.data.userId,
|
||||
response.data.accessJwt,
|
||||
response.data.refreshJwt,
|
||||
);
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
notifications.error({
|
||||
message: t('unexpected_error'),
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ import { isPasswordNotValidMessage, isPasswordValid } from 'pages/SignUp/utils';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import { Password } from '../styles';
|
||||
|
||||
@ -44,36 +45,22 @@ function PasswordContainer(): JSX.Element {
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const { statusCode, error } = await changeMyPassword({
|
||||
await changeMyPassword({
|
||||
newPassword: updatePassword,
|
||||
oldPassword: currentPassword,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (statusCode === 200) {
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notifications.error({
|
||||
message:
|
||||
error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
message: (error as APIError).error.error.code,
|
||||
description: (error as APIError).error.error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ import { PencilIcon } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import { NameInput } from '../styles';
|
||||
|
||||
@ -15,7 +16,9 @@ function UserInfo(): JSX.Element {
|
||||
const { user, org, updateUser } = useAppContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [changedName, setChangedName] = useState<string>(user?.name || '');
|
||||
const [changedName, setChangedName] = useState<string>(
|
||||
user?.displayName || '',
|
||||
);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
@ -27,34 +30,25 @@ function UserInfo(): JSX.Element {
|
||||
const onClickUpdateHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { statusCode } = await editUser({
|
||||
name: changedName,
|
||||
await editUser({
|
||||
displayName: changedName,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (statusCode === 200) {
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
updateUser({
|
||||
...user,
|
||||
name: changedName,
|
||||
});
|
||||
} else {
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
updateUser({
|
||||
...user,
|
||||
displayName: changedName,
|
||||
});
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
|
@ -4,7 +4,7 @@ import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Input, Select, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import inviteUsers from 'api/v1/invite/bulk/create';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { cloneDeep, debounce, isEmpty } from 'lodash-es';
|
||||
import {
|
||||
ArrowLeft,
|
||||
@ -17,12 +17,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import {
|
||||
FailedInvite,
|
||||
InviteUsersResponse,
|
||||
SuccessfulInvite,
|
||||
} from 'types/api/user/inviteUsers';
|
||||
import APIError from 'types/api/error';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
interface TeamMember {
|
||||
@ -55,20 +50,7 @@ function InviteTeamMembers({
|
||||
{},
|
||||
);
|
||||
const [hasInvalidEmails, setHasInvalidEmails] = useState<boolean>(false);
|
||||
|
||||
const [hasErrors, setHasErrors] = useState<boolean>(true);
|
||||
|
||||
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 { notifications } = useNotifications();
|
||||
|
||||
const defaultTeamMember: TeamMember = {
|
||||
email: '',
|
||||
@ -122,92 +104,32 @@ function InviteTeamMembers({
|
||||
return isValid;
|
||||
};
|
||||
|
||||
const parseInviteUsersSuccessResponse = (
|
||||
response: SuccessfulInvite[],
|
||||
): string[] => response.map((invite) => `${invite.email} - Invite Sent`);
|
||||
|
||||
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 handleInviteUsersSuccess = (
|
||||
response: SuccessResponse<InviteUsersResponse>,
|
||||
): 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);
|
||||
|
||||
setInviteUsersSuccessResponse(successfulInvites);
|
||||
|
||||
logEvent('Org Onboarding: Invite Team Members Success', {
|
||||
teamMembers: teamMembersToInvite,
|
||||
totalInvites: inviteUsersResponse.summary.total_invites,
|
||||
successfulInvites: inviteUsersResponse.summary.successful_invites,
|
||||
failedInvites: inviteUsersResponse.summary.failed_invites,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
setDisableNextButton(false);
|
||||
onNext();
|
||||
}, 1000);
|
||||
} else if (inviteUsersResponse?.status === 'partial_success') {
|
||||
const successfulInvites = parseInviteUsersSuccessResponse(
|
||||
inviteUsersResponse.successful_invites,
|
||||
);
|
||||
|
||||
setInviteUsersSuccessResponse(successfulInvites);
|
||||
|
||||
logEvent('Org Onboarding: Invite Team Members Partial Success', {
|
||||
teamMembers: teamMembersToInvite,
|
||||
totalInvites: inviteUsersResponse.summary.total_invites,
|
||||
successfulInvites: inviteUsersResponse.summary.successful_invites,
|
||||
failedInvites: inviteUsersResponse.summary.failed_invites,
|
||||
});
|
||||
|
||||
if (inviteUsersResponse.failed_invites.length > 0) {
|
||||
setHasErrors(true);
|
||||
|
||||
setInviteUsersErrorResponse(
|
||||
parseInviteUsersErrorResponse(inviteUsersResponse.failed_invites),
|
||||
);
|
||||
}
|
||||
}
|
||||
const handleInviteUsersSuccess = (): void => {
|
||||
logEvent('Org Onboarding: Invite Team Members Success', {
|
||||
teamMembers: teamMembersToInvite,
|
||||
});
|
||||
notifications.success({
|
||||
message: 'Invites sent successfully!',
|
||||
});
|
||||
setTimeout(() => {
|
||||
onNext();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const { mutate: sendInvites, isLoading: isSendingInvites } = useMutation(
|
||||
inviteUsers,
|
||||
{
|
||||
onSuccess: (response: SuccessResponse<InviteUsersResponse>): void => {
|
||||
handleInviteUsersSuccess(response);
|
||||
onSuccess: (): void => {
|
||||
handleInviteUsersSuccess();
|
||||
},
|
||||
onError: (error: AxiosError): void => {
|
||||
onError: (error: APIError): void => {
|
||||
logEvent('Org Onboarding: Invite Team Members Failed', {
|
||||
teamMembers: teamMembersToInvite,
|
||||
});
|
||||
|
||||
handleError(error);
|
||||
notifications.error({
|
||||
message: error.getErrorCode(),
|
||||
description: error.getErrorMessage(),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@ -215,15 +137,9 @@ function InviteTeamMembers({
|
||||
const handleNext = (): void => {
|
||||
if (validateAllUsers()) {
|
||||
setTeamMembers(teamMembersToInvite || []);
|
||||
|
||||
setHasInvalidEmails(false);
|
||||
setError(null);
|
||||
setHasErrors(false);
|
||||
setInviteUsersErrorResponse(null);
|
||||
setInviteUsersSuccessResponse(null);
|
||||
|
||||
sendInvites({
|
||||
users: teamMembersToInvite || [],
|
||||
invites: teamMembersToInvite || [],
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -356,63 +272,8 @@ function InviteTeamMembers({
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="error-message-container">
|
||||
<Typography.Text className="error-message" type="danger">
|
||||
<TriangleAlert size={14} /> {error}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasErrors && (
|
||||
<>
|
||||
{/* show only when invites are sent successfully & partial error is present */}
|
||||
{inviteUsersSuccessResponse && inviteUsersErrorResponse && (
|
||||
<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>
|
||||
)}
|
||||
|
||||
<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>
|
||||
|
||||
{/* Partially sent invites */}
|
||||
{inviteUsersSuccessResponse && inviteUsersErrorResponse && (
|
||||
<div className="partially-sent-invites-container">
|
||||
<Typography.Text className="partially-sent-invites-message">
|
||||
<TriangleAlert size={14} />
|
||||
Some invites were sent successfully. Please fix the errors above and
|
||||
resend invites.
|
||||
</Typography.Text>
|
||||
|
||||
<Typography.Text className="partially-sent-invites-message">
|
||||
You can click on I'll do this later to go to next step.
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="next-prev-container">
|
||||
<Button type="default" className="next-button" onClick={onBack}>
|
||||
<ArrowLeft size={14} />
|
||||
@ -423,7 +284,7 @@ function InviteTeamMembers({
|
||||
type="primary"
|
||||
className="next-button"
|
||||
onClick={handleNext}
|
||||
loading={isSendingInvites || isLoading || disableNextButton}
|
||||
loading={isSendingInvites || isLoading}
|
||||
>
|
||||
Send Invites
|
||||
<ArrowRight size={14} />
|
||||
@ -435,7 +296,7 @@ function InviteTeamMembers({
|
||||
type="link"
|
||||
className="do-later-button"
|
||||
onClick={handleDoLater}
|
||||
disabled={isSendingInvites || disableNextButton}
|
||||
disabled={isSendingInvites}
|
||||
>
|
||||
{isLoading && <Loader2 className="animate-spin" size={16} />}
|
||||
|
||||
|
@ -198,7 +198,7 @@ function OrgQuestions({
|
||||
return (
|
||||
<div className="questions-container">
|
||||
<Typography.Title level={3} className="title">
|
||||
Welcome, {user?.name}!
|
||||
Welcome, {user?.displayName}!
|
||||
</Typography.Title>
|
||||
<Typography.Paragraph className="sub-title">
|
||||
We'll help you get the most out of SigNoz, whether you're new to
|
||||
|
@ -4,17 +4,12 @@ import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Input, Select, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import inviteUsers from 'api/v1/invite/bulk/create';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { cloneDeep, debounce, isEmpty } from 'lodash-es';
|
||||
import { ArrowRight, CheckCircle, Plus, TriangleAlert, X } from 'lucide-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import {
|
||||
FailedInvite,
|
||||
InviteUsersResponse,
|
||||
SuccessfulInvite,
|
||||
} from 'types/api/user/inviteUsers';
|
||||
import APIError from 'types/api/error';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
interface TeamMember {
|
||||
@ -55,20 +50,7 @@ function InviteTeamMembers({
|
||||
{},
|
||||
);
|
||||
const [hasInvalidEmails, setHasInvalidEmails] = useState<boolean>(false);
|
||||
|
||||
const [hasErrors, setHasErrors] = useState<boolean>(true);
|
||||
|
||||
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 { notifications } = useNotifications();
|
||||
|
||||
const defaultTeamMember: TeamMember = {
|
||||
email: '',
|
||||
@ -122,91 +104,28 @@ function InviteTeamMembers({
|
||||
return isValid;
|
||||
};
|
||||
|
||||
const parseInviteUsersSuccessResponse = (
|
||||
response: SuccessfulInvite[],
|
||||
): string[] => response.map((invite) => `${invite.email} - Invite Sent`);
|
||||
|
||||
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 handleInviteUsersSuccess = (
|
||||
response: SuccessResponse<InviteUsersResponse>,
|
||||
): void => {
|
||||
const inviteUsersResponse = response.payload as InviteUsersResponse;
|
||||
|
||||
if (inviteUsersResponse?.status === 'success') {
|
||||
const successfulInvites = parseInviteUsersSuccessResponse(
|
||||
inviteUsersResponse.successful_invites,
|
||||
);
|
||||
|
||||
logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.INVITE_TEAM_MEMBER_SUCCESS}`,
|
||||
{
|
||||
teamMembers: teamMembersToInvite,
|
||||
successfulInvites,
|
||||
failedInvites: inviteUsersResponse?.failed_invites || [],
|
||||
},
|
||||
);
|
||||
|
||||
setDisableNextButton(true);
|
||||
|
||||
setError(null);
|
||||
setHasErrors(false);
|
||||
setInviteUsersErrorResponse(null);
|
||||
|
||||
setInviteUsersSuccessResponse(successfulInvites);
|
||||
|
||||
setTimeout(() => {
|
||||
setDisableNextButton(false);
|
||||
onNext();
|
||||
}, 1000);
|
||||
} else if (inviteUsersResponse?.status === 'partial_success') {
|
||||
const successfulInvites = parseInviteUsersSuccessResponse(
|
||||
inviteUsersResponse.successful_invites,
|
||||
);
|
||||
|
||||
logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.INVITE_TEAM_MEMBER_PARTIAL_SUCCESS}`,
|
||||
{
|
||||
teamMembers: teamMembersToInvite,
|
||||
successfulInvites,
|
||||
failedInvites: inviteUsersResponse?.failed_invites || [],
|
||||
},
|
||||
);
|
||||
|
||||
setInviteUsersSuccessResponse(successfulInvites);
|
||||
|
||||
if (inviteUsersResponse.failed_invites.length > 0) {
|
||||
setHasErrors(true);
|
||||
|
||||
setInviteUsersErrorResponse(
|
||||
parseInviteUsersErrorResponse(inviteUsersResponse.failed_invites),
|
||||
);
|
||||
}
|
||||
}
|
||||
const handleInviteUsersSuccess = (): void => {
|
||||
logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.INVITE_TEAM_MEMBER_SUCCESS}`,
|
||||
{
|
||||
teamMembers: teamMembersToInvite,
|
||||
},
|
||||
);
|
||||
setTimeout(() => {
|
||||
onNext();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const { mutate: sendInvites, isLoading: isSendingInvites } = useMutation(
|
||||
inviteUsers,
|
||||
{
|
||||
onSuccess: (response: SuccessResponse<InviteUsersResponse>): void => {
|
||||
handleInviteUsersSuccess(response);
|
||||
onSuccess: (): void => {
|
||||
handleInviteUsersSuccess();
|
||||
notifications.success({
|
||||
message: 'Invites sent successfully!',
|
||||
});
|
||||
},
|
||||
onError: (error: AxiosError): void => {
|
||||
onError: (error: APIError): void => {
|
||||
logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.INVITE_TEAM_MEMBER_FAILED}`,
|
||||
{
|
||||
@ -214,8 +133,10 @@ function InviteTeamMembers({
|
||||
error,
|
||||
},
|
||||
);
|
||||
|
||||
handleError(error);
|
||||
notifications.error({
|
||||
message: error.getErrorCode(),
|
||||
description: error.getErrorMessage(),
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
@ -232,13 +153,8 @@ function InviteTeamMembers({
|
||||
);
|
||||
|
||||
setHasInvalidEmails(false);
|
||||
setError(null);
|
||||
setHasErrors(false);
|
||||
setInviteUsersErrorResponse(null);
|
||||
setInviteUsersSuccessResponse(null);
|
||||
|
||||
sendInvites({
|
||||
users: teamMembersToInvite || [],
|
||||
invites: teamMembersToInvite || [],
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -347,63 +263,7 @@ function InviteTeamMembers({
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="error-message-container">
|
||||
<Typography.Text className="error-message" type="danger">
|
||||
<TriangleAlert size={14} /> {error}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasErrors && (
|
||||
<>
|
||||
{/* show only when invites are sent successfully & partial error is present */}
|
||||
{inviteUsersSuccessResponse && inviteUsersErrorResponse && (
|
||||
<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>
|
||||
)}
|
||||
|
||||
<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>
|
||||
|
||||
{/* Partially sent invites */}
|
||||
{inviteUsersSuccessResponse && inviteUsersErrorResponse && (
|
||||
<div className="partially-sent-invites-container">
|
||||
<Typography.Text className="partially-sent-invites-message">
|
||||
<TriangleAlert size={14} />
|
||||
Some invites were sent successfully. Please fix the errors above and
|
||||
resend invites.
|
||||
</Typography.Text>
|
||||
|
||||
<Typography.Text className="partially-sent-invites-message">
|
||||
You can click on I'll do this later to go to next step.
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="next-prev-container">
|
||||
<Button
|
||||
type="default"
|
||||
@ -418,7 +278,7 @@ function InviteTeamMembers({
|
||||
type="primary"
|
||||
className="next-button periscope-btn primary"
|
||||
onClick={handleNext}
|
||||
loading={isSendingInvites || isLoading || disableNextButton}
|
||||
loading={isSendingInvites || isLoading}
|
||||
>
|
||||
Send Invites
|
||||
<ArrowRight size={14} />
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import APIError from 'types/api/error';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
import { InputGroup, SelectDrawer, Title } from './styles';
|
||||
@ -73,26 +74,13 @@ function EditMembersDetails({
|
||||
const response = await getResetPasswordToken({
|
||||
userId: id || '',
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
setPasswordLink(getPasswordLink(response.payload.token));
|
||||
} else {
|
||||
notifications.error({
|
||||
message:
|
||||
response.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
setPasswordLink(getPasswordLink(response.data.token));
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Button, Form, Modal } from 'antd';
|
||||
import { FormInstance } from 'antd/lib';
|
||||
import sendInvite from 'api/v1/invite/create';
|
||||
import getPendingInvites from 'api/v1/invite/getPendingInvites';
|
||||
import get from 'api/v1/invite/get';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
@ -14,7 +14,9 @@ import {
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { PayloadProps } from 'types/api/user/getPendingInvites';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
import { PendingInvite } from 'types/api/user/getPendingInvites';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
import InviteTeamMembers from '../InviteTeamMembers';
|
||||
@ -31,6 +33,7 @@ export interface InviteUserModalProps {
|
||||
interface DataProps {
|
||||
key: number;
|
||||
name: string;
|
||||
id: string;
|
||||
email: string;
|
||||
accessLevel: ROLES;
|
||||
inviteLink: string;
|
||||
@ -50,17 +53,21 @@ function InviteUserModal(props: InviteUserModalProps): JSX.Element {
|
||||
const [isInvitingMembers, setIsInvitingMembers] = useState<boolean>(false);
|
||||
const [modalForm] = Form.useForm<InviteMemberFormValues>(form);
|
||||
|
||||
const getPendingInvitesResponse = useQuery({
|
||||
queryFn: getPendingInvites,
|
||||
const getPendingInvitesResponse = useQuery<
|
||||
SuccessResponseV2<PendingInvite[]>,
|
||||
APIError
|
||||
>({
|
||||
queryFn: get,
|
||||
queryKey: ['getPendingInvites', user?.accessJwt],
|
||||
enabled: shouldCallApi,
|
||||
});
|
||||
|
||||
const getParsedInviteData = useCallback(
|
||||
(payload: PayloadProps = []) =>
|
||||
(payload: PendingInvite[] = []) =>
|
||||
payload?.map((data) => ({
|
||||
key: data.createdAt,
|
||||
name: data?.name,
|
||||
id: data.id,
|
||||
email: data.email,
|
||||
accessLevel: data.role,
|
||||
inviteLink: `${window.location.origin}${ROUTES.SIGN_UP}?token=${data.token}`,
|
||||
@ -71,16 +78,16 @@ function InviteUserModal(props: InviteUserModalProps): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (
|
||||
getPendingInvitesResponse.status === 'success' &&
|
||||
getPendingInvitesResponse?.data?.payload
|
||||
getPendingInvitesResponse?.data?.data
|
||||
) {
|
||||
const data = getParsedInviteData(
|
||||
getPendingInvitesResponse?.data?.payload || [],
|
||||
getPendingInvitesResponse?.data?.data || [],
|
||||
);
|
||||
setDataSource?.(data);
|
||||
}
|
||||
}, [
|
||||
getParsedInviteData,
|
||||
getPendingInvitesResponse?.data?.payload,
|
||||
getPendingInvitesResponse?.data?.data,
|
||||
getPendingInvitesResponse.status,
|
||||
setDataSource,
|
||||
]);
|
||||
@ -91,33 +98,30 @@ function InviteUserModal(props: InviteUserModalProps): JSX.Element {
|
||||
setIsInvitingMembers?.(true);
|
||||
values?.members?.forEach(
|
||||
async (member): Promise<void> => {
|
||||
const { error, statusCode } = await sendInvite({
|
||||
email: member.email,
|
||||
name: member?.name,
|
||||
role: member.role,
|
||||
frontendBaseUrl: window.location.origin,
|
||||
});
|
||||
|
||||
if (statusCode !== 200) {
|
||||
notifications.error({
|
||||
message:
|
||||
error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
try {
|
||||
await sendInvite({
|
||||
email: member.email,
|
||||
name: member?.name,
|
||||
role: member.role,
|
||||
frontendBaseUrl: window.location.origin,
|
||||
});
|
||||
} else if (statusCode === 200) {
|
||||
|
||||
notifications.success({
|
||||
message: 'Invite sent successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
setTimeout(async () => {
|
||||
const { data, status } = await getPendingInvitesResponse.refetch();
|
||||
if (status === 'success' && data.payload) {
|
||||
setDataSource?.(getParsedInviteData(data?.payload || []));
|
||||
if (status === 'success' && data.data) {
|
||||
setDataSource?.(getParsedInviteData(data?.data || []));
|
||||
}
|
||||
setIsInvitingMembers?.(false);
|
||||
toggleModal(false);
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { Button, Modal, Space, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import getOrgUser from 'api/v1/user/getOrgUser';
|
||||
import getAll from 'api/v1/user/get';
|
||||
import deleteUser from 'api/v1/user/id/delete';
|
||||
import editUserApi from 'api/v1/user/id/update';
|
||||
import updateRole from 'api/v1/user/id/updateRole';
|
||||
import update from 'api/v1/user/id/update';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import dayjs from 'dayjs';
|
||||
@ -12,6 +11,9 @@ import { useAppContext } from 'providers/App/App';
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
import { UserResponse } from 'types/api/user/getUsers';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
import DeleteMembersDetails from '../DeleteMembersDetails';
|
||||
@ -84,79 +86,45 @@ function UserFunction({
|
||||
const onDeleteHandler = async (): Promise<void> => {
|
||||
try {
|
||||
setIsDeleteLoading(true);
|
||||
const response = await deleteUser({
|
||||
await deleteUser({
|
||||
userId: id,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
onDelete();
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
setIsDeleteModalVisible(false);
|
||||
} else {
|
||||
notifications.error({
|
||||
message:
|
||||
response.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
onDelete();
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
setIsDeleteModalVisible(false);
|
||||
setIsDeleteLoading(false);
|
||||
} catch (error) {
|
||||
setIsDeleteLoading(false);
|
||||
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onInviteMemberHandler = async (): Promise<void> => {
|
||||
const onEditMemberDetails = async (): Promise<void> => {
|
||||
try {
|
||||
setIsUpdateLoading(true);
|
||||
const [editUserResponse, updateRoleResponse] = await Promise.all([
|
||||
editUserApi({
|
||||
userId: id,
|
||||
name: updatedName,
|
||||
await update({
|
||||
userId: id,
|
||||
displayName: updatedName,
|
||||
role,
|
||||
});
|
||||
onUpdateDetailsHandler();
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
updateRole({
|
||||
group_name: role,
|
||||
userId: id,
|
||||
}),
|
||||
]);
|
||||
|
||||
if (
|
||||
editUserResponse.statusCode === 200 &&
|
||||
updateRoleResponse.statusCode === 200
|
||||
) {
|
||||
onUpdateDetailsHandler();
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notifications.error({
|
||||
message:
|
||||
editUserResponse.error ||
|
||||
updateRoleResponse.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
});
|
||||
setIsUpdateLoading(false);
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
setIsUpdateLoading(false);
|
||||
}
|
||||
@ -193,7 +161,7 @@ function UserFunction({
|
||||
</Button>,
|
||||
<Button
|
||||
key="Invite_team_members"
|
||||
onClick={onInviteMemberHandler}
|
||||
onClick={onEditMemberDetails}
|
||||
type="primary"
|
||||
disabled={isUpdateLoading}
|
||||
loading={isUpdateLoading}
|
||||
@ -230,28 +198,28 @@ function UserFunction({
|
||||
|
||||
function Members(): JSX.Element {
|
||||
const { org } = useAppContext();
|
||||
const { status, data, isLoading } = useQuery({
|
||||
queryFn: () =>
|
||||
getOrgUser({
|
||||
orgId: (org || [])[0].id,
|
||||
}),
|
||||
const { status, data, isLoading } = useQuery<
|
||||
SuccessResponseV2<UserResponse[]>,
|
||||
APIError
|
||||
>({
|
||||
queryFn: () => getAll(),
|
||||
queryKey: ['getOrgUser', org?.[0].id],
|
||||
});
|
||||
|
||||
const [dataSource, setDataSource] = useState<DataType[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'success' && data?.payload && Array.isArray(data.payload)) {
|
||||
const updatedData: DataType[] = data?.payload?.map((e) => ({
|
||||
if (status === 'success' && data?.data && Array.isArray(data.data)) {
|
||||
const updatedData: DataType[] = data?.data?.map((e) => ({
|
||||
accessLevel: e.role,
|
||||
email: e.email,
|
||||
id: String(e.id),
|
||||
joinedOn: String(e.createdAt),
|
||||
name: e.name,
|
||||
name: e.displayName,
|
||||
}));
|
||||
setDataSource(updatedData);
|
||||
}
|
||||
}, [data?.payload, status]);
|
||||
}, [data?.data, status]);
|
||||
|
||||
const columns: ColumnsType<DataType> = [
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Form, Space, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import getPendingInvites from 'api/v1/invite/getPendingInvites';
|
||||
import get from 'api/v1/invite/get';
|
||||
import deleteInvite from 'api/v1/invite/id/delete';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { INVITE_MEMBERS_HASH } from 'constants/app';
|
||||
@ -13,7 +13,9 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { PayloadProps } from 'types/api/user/getPendingInvites';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
import { PendingInvite } from 'types/api/user/getPendingInvites';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
import InviteUserModal from '../InviteUserModal/InviteUserModal';
|
||||
@ -46,8 +48,11 @@ function PendingInvitesContainer(): JSX.Element {
|
||||
}
|
||||
}, [state.error, state.value, t, notifications]);
|
||||
|
||||
const getPendingInvitesResponse = useQuery({
|
||||
queryFn: getPendingInvites,
|
||||
const getPendingInvitesResponse = useQuery<
|
||||
SuccessResponseV2<PendingInvite[]>,
|
||||
APIError
|
||||
>({
|
||||
queryFn: get,
|
||||
queryKey: ['getPendingInvites', user?.accessJwt],
|
||||
});
|
||||
|
||||
@ -66,10 +71,11 @@ function PendingInvitesContainer(): JSX.Element {
|
||||
const { hash } = useLocation();
|
||||
|
||||
const getParsedInviteData = useCallback(
|
||||
(payload: PayloadProps = []) =>
|
||||
(payload: PendingInvite[] = []) =>
|
||||
payload?.map((data) => ({
|
||||
key: data.createdAt,
|
||||
name: data.name,
|
||||
id: data.id,
|
||||
email: data.email,
|
||||
accessLevel: data.role,
|
||||
inviteLink: `${window.location.origin}${ROUTES.SIGN_UP}?token=${data.token}`,
|
||||
@ -86,54 +92,42 @@ function PendingInvitesContainer(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (
|
||||
getPendingInvitesResponse.status === 'success' &&
|
||||
getPendingInvitesResponse?.data?.payload
|
||||
getPendingInvitesResponse?.data?.data
|
||||
) {
|
||||
const data = getParsedInviteData(
|
||||
getPendingInvitesResponse?.data?.payload || [],
|
||||
getPendingInvitesResponse?.data?.data || [],
|
||||
);
|
||||
setDataSource(data);
|
||||
}
|
||||
}, [
|
||||
getParsedInviteData,
|
||||
getPendingInvitesResponse?.data?.payload,
|
||||
getPendingInvitesResponse?.data?.data,
|
||||
getPendingInvitesResponse.status,
|
||||
]);
|
||||
|
||||
const onRevokeHandler = async (email: string): Promise<void> => {
|
||||
const onRevokeHandler = async (id: string): Promise<void> => {
|
||||
try {
|
||||
const response = await deleteInvite({
|
||||
email,
|
||||
await deleteInvite({
|
||||
id,
|
||||
});
|
||||
if (response.statusCode === 200) {
|
||||
// remove from the client data
|
||||
const index = dataSource.findIndex((e) => e.email === email);
|
||||
|
||||
if (index !== -1) {
|
||||
setDataSource([
|
||||
...dataSource.slice(0, index),
|
||||
...dataSource.slice(index + 1, dataSource.length),
|
||||
]);
|
||||
}
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notifications.error({
|
||||
message:
|
||||
response.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
// remove from the client data
|
||||
const index = dataSource.findIndex((e) => e.id === id);
|
||||
if (index !== -1) {
|
||||
setDataSource([
|
||||
...dataSource.slice(0, index),
|
||||
...dataSource.slice(index + 1, dataSource.length),
|
||||
]);
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -170,9 +164,7 @@ function PendingInvitesContainer(): JSX.Element {
|
||||
key: 'Action',
|
||||
render: (_, record): JSX.Element => (
|
||||
<Space direction="horizontal">
|
||||
<Typography.Link
|
||||
onClick={(): Promise<void> => onRevokeHandler(record.email)}
|
||||
>
|
||||
<Typography.Link onClick={(): Promise<void> => onRevokeHandler(record.id)}>
|
||||
Revoke
|
||||
</Typography.Link>
|
||||
<Typography.Link
|
||||
@ -242,6 +234,7 @@ export interface InviteTeamMembersProps {
|
||||
interface DataProps {
|
||||
key: number;
|
||||
name: string;
|
||||
id: string;
|
||||
email: string;
|
||||
accessLevel: ROLES;
|
||||
inviteLink: string;
|
||||
|
@ -38,7 +38,7 @@ function AddNewPipeline({
|
||||
id: v4(),
|
||||
orderId: (currPipelineData?.length || 0) + 1,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdBy: user?.name || '',
|
||||
createdBy: user?.displayName || '',
|
||||
name: values.name,
|
||||
alias: values.name.replace(/\s/g, ''),
|
||||
description: values.description,
|
||||
|
@ -10,6 +10,7 @@ import { Label } from 'pages/SignUp/styles';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation } from 'react-use';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import { ButtonContainer, FormContainer, FormWrapper } from './styles';
|
||||
|
||||
@ -43,35 +44,24 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element {
|
||||
setLoading(true);
|
||||
const { password } = form.getFieldsValue();
|
||||
|
||||
const response = await resetPasswordApi({
|
||||
await resetPasswordApi({
|
||||
password,
|
||||
token: token || '',
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
history.push(ROUTES.LOGIN);
|
||||
} else {
|
||||
notifications.error({
|
||||
message:
|
||||
response.error ||
|
||||
t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
}
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
history.push(ROUTES.LOGIN);
|
||||
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -69,7 +69,7 @@ function SideNav(): JSX.Element {
|
||||
|
||||
const userSettingsMenuItem = {
|
||||
key: ROUTES.MY_SETTINGS,
|
||||
label: user?.name || 'User',
|
||||
label: user?.displayName || 'User',
|
||||
icon: <UserCircle size={16} />,
|
||||
};
|
||||
|
||||
|
@ -15,24 +15,24 @@ export function useResizeObserver<T extends HTMLElement>(
|
||||
height: ref.current?.clientHeight || 0,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
const handleResize = debounce((entries: ResizeObserverEntry[]) => {
|
||||
const entry = entries[0];
|
||||
if (entry) {
|
||||
const { width, height } = entry.contentRect;
|
||||
setSize({ width, height });
|
||||
}
|
||||
}, debounceTime);
|
||||
const handleResize = debounce((entries: ResizeObserverEntry[]) => {
|
||||
const entry = entries[0];
|
||||
if (entry) {
|
||||
const { width, height } = entry.contentRect;
|
||||
setSize({ width, height });
|
||||
}
|
||||
}, debounceTime);
|
||||
|
||||
const ro = new ResizeObserver(handleResize);
|
||||
ro.observe(ref.current);
|
||||
|
||||
return (): void => {
|
||||
ro.disconnect();
|
||||
};
|
||||
const ro = new ResizeObserver(handleResize);
|
||||
const referenceNode = ref.current;
|
||||
if (referenceNode) {
|
||||
ro.observe(referenceNode);
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
if (referenceNode) ro.disconnect();
|
||||
};
|
||||
}, [ref, debounceTime]);
|
||||
|
||||
return size;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import getUser from 'api/v1/user/id/get';
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/user/getUser';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { UserResponse } from 'types/api/user/getUser';
|
||||
|
||||
const useGetUser = (userId: string, isLoggedIn: boolean): UseGetUser =>
|
||||
useQuery({
|
||||
@ -10,9 +10,6 @@ const useGetUser = (userId: string, isLoggedIn: boolean): UseGetUser =>
|
||||
enabled: !!userId && !!isLoggedIn,
|
||||
});
|
||||
|
||||
type UseGetUser = UseQueryResult<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse,
|
||||
unknown
|
||||
>;
|
||||
type UseGetUser = UseQueryResult<SuccessResponseV2<UserResponse>, unknown>;
|
||||
|
||||
export default useGetUser;
|
||||
|
@ -34,8 +34,8 @@ export const handlers = [
|
||||
res(ctx.status(200), ctx.json(topLevelOperationSuccessResponse)),
|
||||
),
|
||||
|
||||
rest.get('http://localhost/api/v1/orgUsers/*', (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(membersResponse)),
|
||||
rest.get('http://localhost/api/v1/user', (req, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ status: '200', data: membersResponse })),
|
||||
),
|
||||
rest.get(
|
||||
'http://localhost/api/v3/autocomplete/attribute_keys',
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Button, Form, Input, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import accept from 'api/v1/invite/id/accept';
|
||||
import getInviteDetails from 'api/v1/invite/id/get';
|
||||
import loginApi from 'api/v1/login/login';
|
||||
import signUpApi from 'api/v1/register/signup';
|
||||
import loginApi from 'api/v1/user/login';
|
||||
import afterLogin from 'AppRoutes/utils';
|
||||
import WelcomeLeftContainer from 'components/WelcomeLeftContainer';
|
||||
import ROUTES from 'constants/routes';
|
||||
@ -12,6 +13,9 @@ import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
import { InviteDetails } from 'types/api/user/getInviteDetails';
|
||||
import { PayloadProps as LoginPrecheckPayloadProps } from 'types/api/user/loginPrecheck';
|
||||
|
||||
import { ButtonContainer, FormContainer, FormWrapper, Label } from './styles';
|
||||
@ -49,7 +53,10 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
const token = params.get('token');
|
||||
const [isDetailsDisable, setIsDetailsDisable] = useState<boolean>(false);
|
||||
|
||||
const getInviteDetailsResponse = useQuery({
|
||||
const getInviteDetailsResponse = useQuery<
|
||||
SuccessResponseV2<InviteDetails>,
|
||||
APIError
|
||||
>({
|
||||
queryFn: () =>
|
||||
getInviteDetails({
|
||||
inviteId: token || '',
|
||||
@ -64,9 +71,9 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (
|
||||
getInviteDetailsResponse.status === 'success' &&
|
||||
getInviteDetailsResponse.data.payload
|
||||
getInviteDetailsResponse.data.data
|
||||
) {
|
||||
const responseDetails = getInviteDetailsResponse.data.payload;
|
||||
const responseDetails = getInviteDetailsResponse.data.data;
|
||||
if (responseDetails.precheck) setPrecheck(responseDetails.precheck);
|
||||
form.setFieldValue('firstName', responseDetails.name);
|
||||
form.setFieldValue('email', responseDetails.email);
|
||||
@ -82,7 +89,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
getInviteDetailsResponse.data?.payload,
|
||||
getInviteDetailsResponse.data?.data,
|
||||
form,
|
||||
getInviteDetailsResponse.status,
|
||||
]);
|
||||
@ -90,22 +97,24 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (
|
||||
getInviteDetailsResponse.status === 'success' &&
|
||||
getInviteDetailsResponse.data?.error
|
||||
getInviteDetailsResponse?.error
|
||||
) {
|
||||
const { error } = getInviteDetailsResponse.data;
|
||||
const { error } = getInviteDetailsResponse;
|
||||
notifications.error({
|
||||
message: error,
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
}, [
|
||||
getInviteDetailsResponse,
|
||||
getInviteDetailsResponse.data,
|
||||
getInviteDetailsResponse.status,
|
||||
notifications,
|
||||
]);
|
||||
|
||||
const isPreferenceVisible = token === null;
|
||||
const isSignUp = token === null;
|
||||
|
||||
const commonHandler = async (values: FormValues): Promise<void> => {
|
||||
const signUp = async (values: FormValues): Promise<void> => {
|
||||
try {
|
||||
const { organizationName, password, firstName, email } = values;
|
||||
const response = await signUpApi({
|
||||
@ -122,22 +131,35 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
password,
|
||||
});
|
||||
|
||||
if (loginResponse.statusCode === 200) {
|
||||
const { payload } = loginResponse;
|
||||
await afterLogin(payload.userId, payload.accessJwt, payload.refreshJwt);
|
||||
} else {
|
||||
notifications.error({
|
||||
message: loginResponse.error || t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notifications.error({
|
||||
message: response.error || t('unexpected_error'),
|
||||
});
|
||||
const { data } = loginResponse;
|
||||
await afterLogin(data.userId, data.accessJwt, data.refreshJwt);
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: t('unexpected_error'),
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const acceptInvite = async (values: FormValues): Promise<void> => {
|
||||
try {
|
||||
const { password, email, firstName } = values;
|
||||
await accept({
|
||||
password,
|
||||
token: params.get('token') || '',
|
||||
displayName: firstName,
|
||||
});
|
||||
const loginResponse = await loginApi({
|
||||
email,
|
||||
password,
|
||||
});
|
||||
const { data } = loginResponse;
|
||||
await afterLogin(data.userId, data.accessJwt, data.refreshJwt);
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -150,34 +172,23 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const values = form.getFieldsValue();
|
||||
const response = await signUpApi({
|
||||
email: values.email,
|
||||
name: values.firstName,
|
||||
orgDisplayName: values.organizationName,
|
||||
password: values.password,
|
||||
token: params.get('token') || undefined,
|
||||
const response = await accept({
|
||||
password: '',
|
||||
token: params.get('token') || '',
|
||||
sourceUrl: encodeURIComponent(window.location.href),
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
if (response.payload?.sso) {
|
||||
if (response.payload?.ssoUrl) {
|
||||
window.location.href = response.payload?.ssoUrl;
|
||||
} else {
|
||||
notifications.error({
|
||||
message: t('failed_to_initiate_login'),
|
||||
});
|
||||
// take user to login page as there is nothing to do here
|
||||
history.push(ROUTES.LOGIN);
|
||||
}
|
||||
if (response.data?.sso) {
|
||||
if (response.data?.ssoUrl) {
|
||||
window.location.href = response.data?.ssoUrl;
|
||||
} else {
|
||||
notifications.error({
|
||||
message: t('failed_to_initiate_login'),
|
||||
});
|
||||
// take user to login page as there is nothing to do here
|
||||
history.push(ROUTES.LOGIN);
|
||||
}
|
||||
} else {
|
||||
notifications.error({
|
||||
message: response.error || t('unexpected_error'),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
@ -205,15 +216,14 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPreferenceVisible) {
|
||||
await commonHandler(values);
|
||||
} else {
|
||||
if (isSignUp) {
|
||||
await signUp(values);
|
||||
logEvent('Account Created Successfully', {
|
||||
email: values.email,
|
||||
name: values.firstName,
|
||||
});
|
||||
|
||||
await commonHandler(values);
|
||||
} else {
|
||||
await acceptInvite(values);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
@ -227,7 +237,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
};
|
||||
|
||||
const getIsNameVisible = (): boolean =>
|
||||
!(form.getFieldValue('firstName') === 0 && !isPreferenceVisible);
|
||||
!(form.getFieldValue('firstName') === 0 && !isSignUp);
|
||||
|
||||
const isNameVisible = getIsNameVisible();
|
||||
|
||||
@ -343,7 +353,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isPreferenceVisible && (
|
||||
{isSignUp && (
|
||||
<Typography.Paragraph
|
||||
italic
|
||||
style={{
|
||||
|
@ -71,10 +71,10 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
error: userFetchError,
|
||||
} = useGetUser(user.id, isLoggedIn);
|
||||
useEffect(() => {
|
||||
if (!isFetchingUser && userData && userData.payload) {
|
||||
if (!isFetchingUser && userData && userData.data) {
|
||||
setUser((prev) => ({
|
||||
...prev,
|
||||
...userData.payload,
|
||||
...userData.data,
|
||||
}));
|
||||
setOrg((prev) => {
|
||||
if (!prev) {
|
||||
@ -82,19 +82,19 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
return [
|
||||
{
|
||||
createdAt: 0,
|
||||
id: userData.payload.orgId,
|
||||
displayName: userData.payload.organization,
|
||||
id: userData.data.orgId,
|
||||
displayName: userData.data.organization,
|
||||
},
|
||||
];
|
||||
}
|
||||
// else mutate the existing entry
|
||||
const orgIndex = prev.findIndex((e) => e.id === userData.payload.orgId);
|
||||
const orgIndex = prev.findIndex((e) => e.id === userData.data.orgId);
|
||||
const updatedOrg: Organization[] = [
|
||||
...prev.slice(0, orgIndex),
|
||||
{
|
||||
createdAt: 0,
|
||||
id: userData.payload.orgId,
|
||||
displayName: userData.payload.organization,
|
||||
id: userData.data.orgId,
|
||||
displayName: userData.data.organization,
|
||||
},
|
||||
...prev.slice(orgIndex + 1, prev.length),
|
||||
];
|
||||
|
@ -2,7 +2,7 @@ import { FeatureFlagProps as FeatureFlags } from 'types/api/features/getFeatures
|
||||
import { PayloadProps as LicensesResModel } from 'types/api/licenses/getAll';
|
||||
import { LicenseV3ResModel, TrialInfo } from 'types/api/licensesV3/getActive';
|
||||
import { Organization } from 'types/api/user/getOrganization';
|
||||
import { PayloadProps as User } from 'types/api/user/getUser';
|
||||
import { UserResponse as User } from 'types/api/user/getUser';
|
||||
import { OrgPreference } from 'types/reducer/app';
|
||||
|
||||
export interface IAppContext {
|
||||
|
@ -17,8 +17,7 @@ function getUserDefaults(): IUser {
|
||||
refreshJwt,
|
||||
id: userId,
|
||||
email: '',
|
||||
name: '',
|
||||
profilePictureURL: '',
|
||||
displayName: '',
|
||||
createdAt: 0,
|
||||
organization: '',
|
||||
orgId: '',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { apiV3 } from 'api/apiV1';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import { Logout } from 'api/utils';
|
||||
import loginApi from 'api/v1/user/login';
|
||||
import loginApi from 'api/v1/login/login';
|
||||
import afterLogin from 'AppRoutes/utils';
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import { LIVE_TAIL_HEARTBEAT_TIMEOUT } from 'constants/liveTail';
|
||||
@ -18,6 +18,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
interface IEventSourceContext {
|
||||
eventSourceInstance: EventSourcePolyfill | null;
|
||||
@ -80,34 +81,24 @@ export function EventSourceProvider({
|
||||
const response = await loginApi({
|
||||
refreshToken: getLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN) || '',
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
// Update tokens in local storage
|
||||
afterLogin(
|
||||
response.payload.userId,
|
||||
response.payload.accessJwt,
|
||||
response.payload.refreshJwt,
|
||||
true,
|
||||
);
|
||||
|
||||
// If token refresh was successful, we'll let the component
|
||||
// handle reconnection through the reconnectDueToError state
|
||||
setReconnectDueToError(true);
|
||||
setIsConnectionError(true);
|
||||
return;
|
||||
}
|
||||
|
||||
notifications.error({ message: 'Sorry, something went wrong' });
|
||||
// If token refresh failed, logout the user
|
||||
if (!eventSourceRef.current) return;
|
||||
eventSourceRef.current.close();
|
||||
afterLogin(
|
||||
response.data.userId,
|
||||
response.data.accessJwt,
|
||||
response.data.refreshJwt,
|
||||
true,
|
||||
);
|
||||
// If token refresh was successful, we'll let the component
|
||||
// handle reconnection through the reconnectDueToError state
|
||||
setReconnectDueToError(true);
|
||||
setIsConnectionError(true);
|
||||
Logout();
|
||||
return;
|
||||
} catch (error) {
|
||||
// If there was an error during token refresh, we'll just
|
||||
// let the component handle the error state
|
||||
notifications.error({ message: 'Sorry, something went wrong' });
|
||||
console.error('Error refreshing token:', error);
|
||||
notifications.error({
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
setIsConnectionError(true);
|
||||
if (!eventSourceRef.current) return;
|
||||
eventSourceRef.current.close();
|
||||
|
@ -145,8 +145,7 @@ export function getAppContextMock(
|
||||
refreshJwt: 'some-refresh-token',
|
||||
id: 'some-user-id',
|
||||
email: 'does-not-matter@signoz.io',
|
||||
name: 'John Doe',
|
||||
profilePictureURL: '',
|
||||
displayName: 'John Doe',
|
||||
createdAt: 1732544623,
|
||||
organization: 'Nightswatch',
|
||||
orgId: 'does-not-matter-id',
|
||||
|
18
frontend/src/types/api/user/accept.ts
Normal file
18
frontend/src/types/api/user/accept.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export interface Props {
|
||||
token: string;
|
||||
password: string;
|
||||
displayName?: string;
|
||||
sourceUrl?: string;
|
||||
}
|
||||
|
||||
export interface LoginPrecheckResponse {
|
||||
sso: boolean;
|
||||
ssoUrl?: string;
|
||||
canSelfRegister?: boolean;
|
||||
isUser: boolean;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
data: LoginPrecheckResponse;
|
||||
status: string;
|
||||
}
|
@ -7,5 +7,6 @@ export interface Props {
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
status: string;
|
||||
data: string;
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { User } from 'types/reducer/app';
|
||||
|
||||
export interface Props {
|
||||
email: User['email'];
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
|
@ -6,4 +6,5 @@ export interface Props {
|
||||
|
||||
export interface PayloadProps {
|
||||
data: string;
|
||||
status: string;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { User } from 'types/reducer/app';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
import { PayloadProps as Payload } from './getUser';
|
||||
|
||||
@ -6,5 +7,6 @@ export type PayloadProps = Payload;
|
||||
|
||||
export interface Props {
|
||||
userId: User['userId'];
|
||||
name: User['name'];
|
||||
displayName: User['displayName'];
|
||||
role?: ROLES;
|
||||
}
|
||||
|
@ -9,9 +9,14 @@ export interface Props {
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
data: InviteDetails;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface InviteDetails {
|
||||
createdAt: number;
|
||||
email: User['email'];
|
||||
name: User['name'];
|
||||
name: User['displayName'];
|
||||
role: ROLES;
|
||||
token: string;
|
||||
organization: Organization['displayName'];
|
||||
|
@ -1,18 +0,0 @@
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
import { Organization } from './getOrganization';
|
||||
|
||||
export interface Props {
|
||||
orgId: Organization['id'];
|
||||
}
|
||||
|
||||
interface OrgMembers {
|
||||
createdAt: number;
|
||||
email: string;
|
||||
name: string;
|
||||
role: ROLES;
|
||||
token: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type PayloadProps = OrgMembers[];
|
@ -4,9 +4,13 @@ import { ROLES } from 'types/roles';
|
||||
export interface PendingInvite {
|
||||
createdAt: number;
|
||||
email: User['email'];
|
||||
name: User['name'];
|
||||
name: User['displayName'];
|
||||
role: ROLES;
|
||||
id: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
export type PayloadProps = PendingInvite[];
|
||||
export type PayloadProps = {
|
||||
data: PendingInvite[];
|
||||
status: string;
|
||||
};
|
||||
|
@ -4,7 +4,12 @@ export interface Props {
|
||||
userId: User['userId'];
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
export interface GetResetPasswordToken {
|
||||
token: string;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
data: GetResetPasswordToken;
|
||||
status: string;
|
||||
}
|
||||
|
@ -6,13 +6,16 @@ export interface Props {
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
export interface UserResponse {
|
||||
createdAt: number;
|
||||
email: string;
|
||||
id: string;
|
||||
name: string;
|
||||
displayName: string;
|
||||
orgId: string;
|
||||
profilePictureURL: string;
|
||||
organization: string;
|
||||
role: ROLES;
|
||||
}
|
||||
export interface PayloadProps {
|
||||
data: UserResponse;
|
||||
status: string;
|
||||
}
|
||||
|
15
frontend/src/types/api/user/getUsers.ts
Normal file
15
frontend/src/types/api/user/getUsers.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
export interface UserResponse {
|
||||
createdAt: number;
|
||||
email: string;
|
||||
id: string;
|
||||
displayName: string;
|
||||
orgId: string;
|
||||
organization: string;
|
||||
role: ROLES;
|
||||
}
|
||||
export interface PayloadProps {
|
||||
data: UserResponse[];
|
||||
status: string;
|
||||
}
|
@ -1,40 +1,12 @@
|
||||
import { User } from 'types/reducer/app';
|
||||
|
||||
import { ErrorResponse } from '..';
|
||||
|
||||
export interface UserProps {
|
||||
name: User['name'];
|
||||
name: User['displayName'];
|
||||
email: User['email'];
|
||||
role: string;
|
||||
frontendBaseUrl: string;
|
||||
}
|
||||
|
||||
export interface UsersProps {
|
||||
users: UserProps[];
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
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[];
|
||||
invites: UserProps[];
|
||||
}
|
||||
|
@ -1,13 +1,18 @@
|
||||
export interface PayloadProps {
|
||||
data: UserLoginResponse;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
email?: string;
|
||||
password?: string;
|
||||
refreshToken?: UserLoginResponse['refreshJwt'];
|
||||
}
|
||||
|
||||
export interface UserLoginResponse {
|
||||
accessJwt: string;
|
||||
accessJwtExpiry: number;
|
||||
refreshJwt: string;
|
||||
refreshJwtExpiry: number;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
email?: string;
|
||||
password?: string;
|
||||
refreshToken?: PayloadProps['refreshJwt'];
|
||||
}
|
||||
|
@ -5,4 +5,5 @@ export interface Props {
|
||||
|
||||
export interface PayloadProps {
|
||||
data: string;
|
||||
status: string;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { User } from 'types/reducer/app';
|
||||
import { ROLES } from 'types/roles';
|
||||
|
||||
export interface Props {
|
||||
name: User['name'];
|
||||
name: User['displayName'];
|
||||
email: User['email'];
|
||||
role: ROLES;
|
||||
frontendBaseUrl: string;
|
||||
@ -10,4 +10,5 @@ export interface Props {
|
||||
|
||||
export interface PayloadProps {
|
||||
data: string;
|
||||
status: string;
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { PayloadProps as ConfigPayload } from 'types/api/dynamicConfigs/getDynamicConfigs';
|
||||
import { PayloadProps as UserPayload } from 'types/api/user/getUser';
|
||||
import { UserResponse as UserPayload } from 'types/api/user/getUser';
|
||||
|
||||
export interface User {
|
||||
accessJwt: string;
|
||||
refreshJwt: string;
|
||||
userId: string;
|
||||
email: UserPayload['email'];
|
||||
name: UserPayload['name'];
|
||||
profilePictureURL: UserPayload['profilePictureURL'];
|
||||
displayName: UserPayload['displayName'];
|
||||
}
|
||||
|
||||
export interface OrgPreference {
|
||||
|
@ -3,6 +3,7 @@
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist/",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"jsx": "react-jsx",
|
||||
|
Loading…
x
Reference in New Issue
Block a user