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