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:
Vikrant Gupta 2025-05-14 23:53:41 +05:30 committed by GitHub
parent 0a2b7ca1d8
commit f525647b40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 601 additions and 1011 deletions

View File

@ -110,6 +110,8 @@ module.exports = {
// eslint rules need to remove
'@typescript-eslint/no-shadow': 'off',
'import/no-cycle': 'off',
// https://typescript-eslint.io/rules/consistent-return/ check the warning for details
'consistent-return': 'off',
'prettier/prettier': [
'error',
{},

View File

@ -1,6 +1,6 @@
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import getOrgUser from 'api/v1/user/getOrgUser';
import getAll from 'api/v1/user/get';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
@ -11,8 +11,11 @@ import { useAppContext } from 'providers/App/App';
import { ReactChild, useCallback, useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { matchPath, useLocation } from 'react-router-dom';
import { SuccessResponseV2 } from 'types/api';
import APIError from 'types/api/error';
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
import { Organization } from 'types/api/user/getOrganization';
import { UserResponse } from 'types/api/user/getUser';
import { USER_ROLES } from 'types/roles';
import { routePermission } from 'utils/permission';
@ -58,12 +61,13 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
const [orgData, setOrgData] = useState<Organization | undefined>(undefined);
const { data: orgUsers, isFetching: isFetchingOrgUsers } = useQuery({
const { data: usersData, isFetching: isFetchingUsers } = useQuery<
SuccessResponseV2<UserResponse[]> | undefined,
APIError
>({
queryFn: () => {
if (orgData && orgData.id !== undefined) {
return getOrgUser({
orgId: orgData.id,
});
return getAll();
}
return undefined;
},
@ -72,23 +76,23 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
});
const checkFirstTimeUser = useCallback((): boolean => {
const users = orgUsers?.payload || [];
const users = usersData?.data || [];
const remainingUsers = users.filter(
(user) => user.email !== 'admin@signoz.cloud',
);
return remainingUsers.length === 1;
}, [orgUsers?.payload]);
}, [usersData?.data]);
useEffect(() => {
if (
isCloudUserVal &&
!isFetchingOrgPreferences &&
orgPreferences &&
!isFetchingOrgUsers &&
orgUsers &&
orgUsers.payload
!isFetchingUsers &&
usersData &&
usersData.data
) {
const isOnboardingComplete = orgPreferences?.find(
(preference: Record<string, any>) => preference.key === 'ORG_ONBOARDING',
@ -108,9 +112,9 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
checkFirstTimeUser,
isCloudUserVal,
isFetchingOrgPreferences,
isFetchingOrgUsers,
isFetchingUsers,
orgPreferences,
orgUsers,
usersData,
pathname,
]);

View File

@ -70,14 +70,14 @@ function App(): JSX.Element {
const orgName =
org && Array.isArray(org) && org.length > 0 ? org[0].displayName : '';
const { name, email, role } = user;
const { displayName, email, role } = user;
const domain = extractDomain(email);
const hostNameParts = hostname.split('.');
const identifyPayload = {
email,
name,
name: displayName,
company_name: orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
@ -106,7 +106,7 @@ function App(): JSX.Element {
Userpilot.identify(email, {
email,
name,
name: displayName,
orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
@ -118,7 +118,7 @@ function App(): JSX.Element {
posthog?.identify(email, {
email,
name,
name: displayName,
orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
@ -144,7 +144,7 @@ function App(): JSX.Element {
) {
window.cioanalytics.reset();
window.cioanalytics.identify(email, {
name: user.name,
name: user.displayName,
email,
role: user.role,
});
@ -258,7 +258,7 @@ function App(): JSX.Element {
window.Intercom('boot', {
app_id: process.env.INTERCOM_APP_ID,
email: user?.email || '',
name: user?.name || '',
name: user?.displayName || '',
});
}
}

View File

@ -2,7 +2,7 @@
/* eslint-disable no-param-reassign */
/* eslint-disable @typescript-eslint/no-explicit-any */
import getLocalStorageApi from 'api/browser/localstorage/get';
import loginApi from 'api/v1/user/login';
import loginApi from 'api/v1/login/login';
import afterLogin from 'AppRoutes/utils';
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
import { ENVIRONMENT } from 'constants/env';
@ -71,15 +71,15 @@ const interceptorRejected = async (
const { response } = value;
// reject the refresh token error
if (response.status === 401 && response.config.url !== '/login') {
try {
const response = await loginApi({
refreshToken: getLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN) || '',
});
if (response.statusCode === 200) {
afterLogin(
response.payload.userId,
response.payload.accessJwt,
response.payload.refreshJwt,
response.data.userId,
response.data.accessJwt,
response.data.refreshJwt,
true,
);
@ -89,23 +89,22 @@ const interceptorRejected = async (
method: value.config.method,
headers: {
...value.config.headers,
Authorization: `Bearer ${response.payload.accessJwt}`,
Authorization: `Bearer ${response.data.accessJwt}`,
},
data: {
...JSON.parse(value.config.data || '{}'),
},
},
);
if (reResponse.status === 200) {
return await Promise.resolve(reResponse);
}
Logout();
return await Promise.reject(reResponse);
}
} catch (error) {
Logout();
}
}
// when refresh token is expired
if (response.status === 401 && response.config.url === '/login') {
Logout();

View File

@ -1,25 +1,26 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/changeMyPassword';
const changeMyPassword = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post(`/changePassword/${props.userId}`, {
const response = await axios.post<PayloadProps>(
`/changePassword/${props.userId}`,
{
...props,
});
},
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};

View File

@ -1,23 +1,27 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/getResetPasswordToken';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import {
GetResetPasswordToken,
PayloadProps,
Props,
} from 'types/api/user/getResetPasswordToken';
const getResetPasswordToken = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<GetResetPasswordToken>> => {
try {
const response = await axios.get(`/getResetPasswordToken/${props.userId}`);
const response = await axios.get<PayloadProps>(
`/getResetPasswordToken/${props.userId}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};

View File

@ -1,25 +1,23 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/resetPassword';
const resetPassword = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post(`/resetPassword`, {
const response = await axios.post<PayloadProps>(`/resetPassword`, {
...props,
});
return {
statusCode: 200,
error: null,
message: response.statusText,
payload: response.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};

View File

@ -1,18 +1,21 @@
import axios from 'api';
import { SuccessResponse } from 'types/api';
import { InviteUsersResponse, UsersProps } from 'types/api/user/inviteUsers';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { UsersProps } from 'types/api/user/inviteUsers';
const inviteUsers = async (
users: UsersProps,
): Promise<SuccessResponse<InviteUsersResponse>> => {
): Promise<SuccessResponseV2<null>> => {
try {
const response = await axios.post(`/invite/bulk`, users);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
httpStatusCode: response.status,
data: null,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default inviteUsers;

View File

@ -1,25 +1,23 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/setInvite';
const sendInvite = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadProps>> => {
try {
const response = await axios.post(`/invite`, {
const response = await axios.post<PayloadProps>(`/invite`, {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};

View 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;

View File

@ -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;

View 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;

View File

@ -1,24 +1,20 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/deleteInvite';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { Props } from 'types/api/user/deleteInvite';
const deleteInvite = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const del = async (props: Props): Promise<SuccessResponseV2<null>> => {
try {
const response = await axios.delete(`/invite/${props.email}`);
const response = await axios.delete(`/invite/${props.id}`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
httpStatusCode: response.status,
data: null,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default deleteInvite;
export default del;

View File

@ -1,25 +1,27 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/getInviteDetails';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import {
InviteDetails,
PayloadProps,
Props,
} from 'types/api/user/getInviteDetails';
const getInviteDetails = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<InviteDetails>> => {
try {
const response = await axios.get(
const response = await axios.get<PayloadProps>(
`/invite/${props.inviteId}?ref=${window.location.href}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};

View File

@ -2,11 +2,11 @@ import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/login';
import { PayloadProps, Props, UserLoginResponse } from 'types/api/user/login';
const login = async (
props: Props,
): Promise<SuccessResponseV2<PayloadProps>> => {
): Promise<SuccessResponseV2<UserLoginResponse>> => {
try {
const response = await axios.post<PayloadProps>(`/login`, {
...props,
@ -14,12 +14,10 @@ const login = async (
return {
httpStatusCode: response.status,
data: response.data,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
// this line is never reached but ts isn't detecting the never type properly for the ErrorResponseHandlerV2
throw error;
}
};

View 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;

View File

@ -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;

View File

@ -1,23 +1,19 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/deleteUser';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { Props } from 'types/api/user/deleteUser';
const deleteUser = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const deleteUser = async (props: Props): Promise<SuccessResponseV2<null>> => {
try {
const response = await axios.delete(`/user/${props.userId}`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
httpStatusCode: response.status,
data: null,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};

View File

@ -1,18 +1,22 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/getUser';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props, UserResponse } from 'types/api/user/getUser';
const getUser = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const response = await axios.get(`/user/${props.userId}`);
): Promise<SuccessResponseV2<UserResponse>> => {
try {
const response = await axios.get<PayloadProps>(`/user/${props.userId}`);
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data,
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getUser;

View File

@ -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;

View File

@ -1,26 +1,23 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/editUser';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { Props } from 'types/api/user/editUser';
const editUser = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
const update = async (props: Props): Promise<SuccessResponseV2<null>> => {
try {
const response = await axios.put(`/user/${props.userId}`, {
Name: props.name,
displayName: props.displayName,
role: props.role,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
httpStatusCode: response.status,
data: null,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default editUser;
export default update;

View File

@ -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;

View File

@ -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;

View File

@ -1,8 +1,8 @@
import { Button, Form, Input, Space, Tooltip, Typography } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import loginApi from 'api/v1/login/login';
import loginPrecheckApi from 'api/v1/login/loginPrecheck';
import loginApi from 'api/v1/user/login';
import getUserVersion from 'api/v1/version/getVersion';
import afterLogin from 'AppRoutes/utils';
import { LOCALSTORAGE } from 'constants/localStorage';
@ -13,6 +13,7 @@ import { useAppContext } from 'providers/App/App';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import APIError from 'types/api/error';
import { PayloadProps as PrecheckResultType } from 'types/api/user/loginPrecheck';
import { FormContainer, FormWrapper, Label, ParentContainer } from './styles';
@ -166,22 +167,18 @@ function Login({
email,
password,
});
if (response.statusCode === 200) {
afterLogin(
response.payload.userId,
response.payload.accessJwt,
response.payload.refreshJwt,
response.data.userId,
response.data.accessJwt,
response.data.refreshJwt,
);
} else {
notifications.error({
message: response.error || t('unexpected_error'),
});
}
setIsLoading(false);
} catch (error) {
setIsLoading(false);
notifications.error({
message: t('unexpected_error'),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
}
};

View File

@ -6,6 +6,7 @@ import { isPasswordNotValidMessage, isPasswordValid } from 'pages/SignUp/utils';
import { useAppContext } from 'providers/App/App';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import APIError from 'types/api/error';
import { Password } from '../styles';
@ -44,36 +45,22 @@ function PasswordContainer(): JSX.Element {
setIsLoading(false);
return;
}
const { statusCode, error } = await changeMyPassword({
await changeMyPassword({
newPassword: updatePassword,
oldPassword: currentPassword,
userId: user.id,
});
if (statusCode === 200) {
notifications.success({
message: t('success', {
ns: 'common',
}),
});
} else {
notifications.error({
message:
error ||
t('something_went_wrong', {
ns: 'common',
}),
});
}
setIsLoading(false);
} catch (error) {
setIsLoading(false);
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
message: (error as APIError).error.error.code,
description: (error as APIError).error.error.message,
});
}
};

View File

@ -8,6 +8,7 @@ import { PencilIcon } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import APIError from 'types/api/error';
import { NameInput } from '../styles';
@ -15,7 +16,9 @@ function UserInfo(): JSX.Element {
const { user, org, updateUser } = useAppContext();
const { t } = useTranslation();
const [changedName, setChangedName] = useState<string>(user?.name || '');
const [changedName, setChangedName] = useState<string>(
user?.displayName || '',
);
const [loading, setLoading] = useState<boolean>(false);
const { notifications } = useNotifications();
@ -27,12 +30,11 @@ function UserInfo(): JSX.Element {
const onClickUpdateHandler = async (): Promise<void> => {
try {
setLoading(true);
const { statusCode } = await editUser({
name: changedName,
await editUser({
displayName: changedName,
userId: user.id,
});
if (statusCode === 200) {
notifications.success({
message: t('success', {
ns: 'common',
@ -40,21 +42,13 @@ function UserInfo(): JSX.Element {
});
updateUser({
...user,
name: changedName,
displayName: changedName,
});
} else {
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
});
}
setLoading(false);
} catch (error) {
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
}
setLoading(false);

View File

@ -4,7 +4,7 @@ import { Color } from '@signozhq/design-tokens';
import { Button, Input, Select, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import inviteUsers from 'api/v1/invite/bulk/create';
import { AxiosError } from 'axios';
import { useNotifications } from 'hooks/useNotifications';
import { cloneDeep, debounce, isEmpty } from 'lodash-es';
import {
ArrowLeft,
@ -17,12 +17,7 @@ import {
} from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { useMutation } from 'react-query';
import { SuccessResponse } from 'types/api';
import {
FailedInvite,
InviteUsersResponse,
SuccessfulInvite,
} from 'types/api/user/inviteUsers';
import APIError from 'types/api/error';
import { v4 as uuid } from 'uuid';
interface TeamMember {
@ -55,20 +50,7 @@ function InviteTeamMembers({
{},
);
const [hasInvalidEmails, setHasInvalidEmails] = useState<boolean>(false);
const [hasErrors, setHasErrors] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const [inviteUsersErrorResponse, setInviteUsersErrorResponse] = useState<
string[] | null
>(null);
const [inviteUsersSuccessResponse, setInviteUsersSuccessResponse] = useState<
string[] | null
>(null);
const [disableNextButton, setDisableNextButton] = useState<boolean>(false);
const { notifications } = useNotifications();
const defaultTeamMember: TeamMember = {
email: '',
@ -122,92 +104,32 @@ function InviteTeamMembers({
return isValid;
};
const parseInviteUsersSuccessResponse = (
response: SuccessfulInvite[],
): string[] => response.map((invite) => `${invite.email} - Invite Sent`);
const parseInviteUsersErrorResponse = (response: FailedInvite[]): string[] =>
response.map((invite) => `${invite.email} - ${invite.error}`);
const handleError = (error: AxiosError): void => {
const errorMessage = error.response?.data as InviteUsersResponse;
if (errorMessage?.status === 'failure') {
setHasErrors(true);
const failedInvitesErrorResponse = parseInviteUsersErrorResponse(
errorMessage.failed_invites,
);
setInviteUsersErrorResponse(failedInvitesErrorResponse);
}
};
const handleInviteUsersSuccess = (
response: SuccessResponse<InviteUsersResponse>,
): void => {
const inviteUsersResponse = response.payload as InviteUsersResponse;
if (inviteUsersResponse?.status === 'success') {
const successfulInvites = parseInviteUsersSuccessResponse(
inviteUsersResponse.successful_invites,
);
setDisableNextButton(true);
setError(null);
setHasErrors(false);
setInviteUsersErrorResponse(null);
setInviteUsersSuccessResponse(successfulInvites);
const handleInviteUsersSuccess = (): void => {
logEvent('Org Onboarding: Invite Team Members Success', {
teamMembers: teamMembersToInvite,
totalInvites: inviteUsersResponse.summary.total_invites,
successfulInvites: inviteUsersResponse.summary.successful_invites,
failedInvites: inviteUsersResponse.summary.failed_invites,
});
notifications.success({
message: 'Invites sent successfully!',
});
setTimeout(() => {
setDisableNextButton(false);
onNext();
}, 1000);
} else if (inviteUsersResponse?.status === 'partial_success') {
const successfulInvites = parseInviteUsersSuccessResponse(
inviteUsersResponse.successful_invites,
);
setInviteUsersSuccessResponse(successfulInvites);
logEvent('Org Onboarding: Invite Team Members Partial Success', {
teamMembers: teamMembersToInvite,
totalInvites: inviteUsersResponse.summary.total_invites,
successfulInvites: inviteUsersResponse.summary.successful_invites,
failedInvites: inviteUsersResponse.summary.failed_invites,
});
if (inviteUsersResponse.failed_invites.length > 0) {
setHasErrors(true);
setInviteUsersErrorResponse(
parseInviteUsersErrorResponse(inviteUsersResponse.failed_invites),
);
}
}
};
const { mutate: sendInvites, isLoading: isSendingInvites } = useMutation(
inviteUsers,
{
onSuccess: (response: SuccessResponse<InviteUsersResponse>): void => {
handleInviteUsersSuccess(response);
onSuccess: (): void => {
handleInviteUsersSuccess();
},
onError: (error: AxiosError): void => {
onError: (error: APIError): void => {
logEvent('Org Onboarding: Invite Team Members Failed', {
teamMembers: teamMembersToInvite,
});
handleError(error);
notifications.error({
message: error.getErrorCode(),
description: error.getErrorMessage(),
});
},
},
);
@ -215,15 +137,9 @@ function InviteTeamMembers({
const handleNext = (): void => {
if (validateAllUsers()) {
setTeamMembers(teamMembersToInvite || []);
setHasInvalidEmails(false);
setError(null);
setHasErrors(false);
setInviteUsersErrorResponse(null);
setInviteUsersSuccessResponse(null);
sendInvites({
users: teamMembersToInvite || [],
invites: teamMembersToInvite || [],
});
}
};
@ -356,62 +272,7 @@ function InviteTeamMembers({
</Typography.Text>
</div>
)}
{error && (
<div className="error-message-container">
<Typography.Text className="error-message" type="danger">
<TriangleAlert size={14} /> {error}
</Typography.Text>
</div>
)}
{hasErrors && (
<>
{/* show only when invites are sent successfully & partial error is present */}
{inviteUsersSuccessResponse && inviteUsersErrorResponse && (
<div className="success-message-container invite-users-success-message-container">
{inviteUsersSuccessResponse?.map((success, index) => (
<Typography.Text
className="success-message"
// eslint-disable-next-line react/no-array-index-key
key={`${success}-${index}`}
>
<CheckCircle size={14} /> {success}
</Typography.Text>
))}
</div>
)}
<div className="error-message-container invite-users-error-message-container">
{inviteUsersErrorResponse?.map((error, index) => (
<Typography.Text
className="error-message"
type="danger"
// eslint-disable-next-line react/no-array-index-key
key={`${error}-${index}`}
>
<TriangleAlert size={14} /> {error}
</Typography.Text>
))}
</div>
</>
)}
</div>
{/* Partially sent invites */}
{inviteUsersSuccessResponse && inviteUsersErrorResponse && (
<div className="partially-sent-invites-container">
<Typography.Text className="partially-sent-invites-message">
<TriangleAlert size={14} />
Some invites were sent successfully. Please fix the errors above and
resend invites.
</Typography.Text>
<Typography.Text className="partially-sent-invites-message">
You can click on I&apos;ll do this later to go to next step.
</Typography.Text>
</div>
)}
<div className="next-prev-container">
<Button type="default" className="next-button" onClick={onBack}>
@ -423,7 +284,7 @@ function InviteTeamMembers({
type="primary"
className="next-button"
onClick={handleNext}
loading={isSendingInvites || isLoading || disableNextButton}
loading={isSendingInvites || isLoading}
>
Send Invites
<ArrowRight size={14} />
@ -435,7 +296,7 @@ function InviteTeamMembers({
type="link"
className="do-later-button"
onClick={handleDoLater}
disabled={isSendingInvites || disableNextButton}
disabled={isSendingInvites}
>
{isLoading && <Loader2 className="animate-spin" size={16} />}

View File

@ -198,7 +198,7 @@ function OrgQuestions({
return (
<div className="questions-container">
<Typography.Title level={3} className="title">
Welcome, {user?.name}!
Welcome, {user?.displayName}!
</Typography.Title>
<Typography.Paragraph className="sub-title">
We&apos;ll help you get the most out of SigNoz, whether you&apos;re new to

View File

@ -4,17 +4,12 @@ import { Color } from '@signozhq/design-tokens';
import { Button, Input, Select, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import inviteUsers from 'api/v1/invite/bulk/create';
import { AxiosError } from 'axios';
import { useNotifications } from 'hooks/useNotifications';
import { cloneDeep, debounce, isEmpty } from 'lodash-es';
import { ArrowRight, CheckCircle, Plus, TriangleAlert, X } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { useMutation } from 'react-query';
import { SuccessResponse } from 'types/api';
import {
FailedInvite,
InviteUsersResponse,
SuccessfulInvite,
} from 'types/api/user/inviteUsers';
import APIError from 'types/api/error';
import { v4 as uuid } from 'uuid';
interface TeamMember {
@ -55,20 +50,7 @@ function InviteTeamMembers({
{},
);
const [hasInvalidEmails, setHasInvalidEmails] = useState<boolean>(false);
const [hasErrors, setHasErrors] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const [inviteUsersErrorResponse, setInviteUsersErrorResponse] = useState<
string[] | null
>(null);
const [inviteUsersSuccessResponse, setInviteUsersSuccessResponse] = useState<
string[] | null
>(null);
const [disableNextButton, setDisableNextButton] = useState<boolean>(false);
const { notifications } = useNotifications();
const defaultTeamMember: TeamMember = {
email: '',
@ -122,91 +104,28 @@ function InviteTeamMembers({
return isValid;
};
const parseInviteUsersSuccessResponse = (
response: SuccessfulInvite[],
): string[] => response.map((invite) => `${invite.email} - Invite Sent`);
const parseInviteUsersErrorResponse = (response: FailedInvite[]): string[] =>
response.map((invite) => `${invite.email} - ${invite.error}`);
const handleError = (error: AxiosError): void => {
const errorMessage = error.response?.data as InviteUsersResponse;
if (errorMessage?.status === 'failure') {
setHasErrors(true);
const failedInvitesErrorResponse = parseInviteUsersErrorResponse(
errorMessage.failed_invites,
);
setInviteUsersErrorResponse(failedInvitesErrorResponse);
}
};
const handleInviteUsersSuccess = (
response: SuccessResponse<InviteUsersResponse>,
): void => {
const inviteUsersResponse = response.payload as InviteUsersResponse;
if (inviteUsersResponse?.status === 'success') {
const successfulInvites = parseInviteUsersSuccessResponse(
inviteUsersResponse.successful_invites,
);
const handleInviteUsersSuccess = (): void => {
logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.INVITE_TEAM_MEMBER_SUCCESS}`,
{
teamMembers: teamMembersToInvite,
successfulInvites,
failedInvites: inviteUsersResponse?.failed_invites || [],
},
);
setDisableNextButton(true);
setError(null);
setHasErrors(false);
setInviteUsersErrorResponse(null);
setInviteUsersSuccessResponse(successfulInvites);
setTimeout(() => {
setDisableNextButton(false);
onNext();
}, 1000);
} else if (inviteUsersResponse?.status === 'partial_success') {
const successfulInvites = parseInviteUsersSuccessResponse(
inviteUsersResponse.successful_invites,
);
logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.INVITE_TEAM_MEMBER_PARTIAL_SUCCESS}`,
{
teamMembers: teamMembersToInvite,
successfulInvites,
failedInvites: inviteUsersResponse?.failed_invites || [],
},
);
setInviteUsersSuccessResponse(successfulInvites);
if (inviteUsersResponse.failed_invites.length > 0) {
setHasErrors(true);
setInviteUsersErrorResponse(
parseInviteUsersErrorResponse(inviteUsersResponse.failed_invites),
);
}
}
};
const { mutate: sendInvites, isLoading: isSendingInvites } = useMutation(
inviteUsers,
{
onSuccess: (response: SuccessResponse<InviteUsersResponse>): void => {
handleInviteUsersSuccess(response);
onSuccess: (): void => {
handleInviteUsersSuccess();
notifications.success({
message: 'Invites sent successfully!',
});
},
onError: (error: AxiosError): void => {
onError: (error: APIError): void => {
logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.INVITE_TEAM_MEMBER_FAILED}`,
{
@ -214,8 +133,10 @@ function InviteTeamMembers({
error,
},
);
handleError(error);
notifications.error({
message: error.getErrorCode(),
description: error.getErrorMessage(),
});
},
},
);
@ -232,13 +153,8 @@ function InviteTeamMembers({
);
setHasInvalidEmails(false);
setError(null);
setHasErrors(false);
setInviteUsersErrorResponse(null);
setInviteUsersSuccessResponse(null);
sendInvites({
users: teamMembersToInvite || [],
invites: teamMembersToInvite || [],
});
}
};
@ -347,63 +263,7 @@ function InviteTeamMembers({
</Typography.Text>
</div>
)}
{error && (
<div className="error-message-container">
<Typography.Text className="error-message" type="danger">
<TriangleAlert size={14} /> {error}
</Typography.Text>
</div>
)}
{hasErrors && (
<>
{/* show only when invites are sent successfully & partial error is present */}
{inviteUsersSuccessResponse && inviteUsersErrorResponse && (
<div className="success-message-container invite-users-success-message-container">
{inviteUsersSuccessResponse?.map((success, index) => (
<Typography.Text
className="success-message"
// eslint-disable-next-line react/no-array-index-key
key={`${success}-${index}`}
>
<CheckCircle size={14} /> {success}
</Typography.Text>
))}
</div>
)}
<div className="error-message-container invite-users-error-message-container">
{inviteUsersErrorResponse?.map((error, index) => (
<Typography.Text
className="error-message"
type="danger"
// eslint-disable-next-line react/no-array-index-key
key={`${error}-${index}`}
>
<TriangleAlert size={14} /> {error}
</Typography.Text>
))}
</div>
</>
)}
</div>
{/* Partially sent invites */}
{inviteUsersSuccessResponse && inviteUsersErrorResponse && (
<div className="partially-sent-invites-container">
<Typography.Text className="partially-sent-invites-message">
<TriangleAlert size={14} />
Some invites were sent successfully. Please fix the errors above and
resend invites.
</Typography.Text>
<Typography.Text className="partially-sent-invites-message">
You can click on I&apos;ll do this later to go to next step.
</Typography.Text>
</div>
)}
<div className="next-prev-container">
<Button
type="default"
@ -418,7 +278,7 @@ function InviteTeamMembers({
type="primary"
className="next-button periscope-btn primary"
onClick={handleNext}
loading={isSendingInvites || isLoading || disableNextButton}
loading={isSendingInvites || isLoading}
>
Send Invites
<ArrowRight size={14} />

View File

@ -13,6 +13,7 @@ import {
} from 'react';
import { useTranslation } from 'react-i18next';
import { useCopyToClipboard } from 'react-use';
import APIError from 'types/api/error';
import { ROLES } from 'types/roles';
import { InputGroup, SelectDrawer, Title } from './styles';
@ -73,26 +74,13 @@ function EditMembersDetails({
const response = await getResetPasswordToken({
userId: id || '',
});
if (response.statusCode === 200) {
setPasswordLink(getPasswordLink(response.payload.token));
} else {
notifications.error({
message:
response.error ||
t('something_went_wrong', {
ns: 'common',
}),
});
}
setPasswordLink(getPasswordLink(response.data.token));
setIsLoading(false);
} catch (error) {
setIsLoading(false);
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
}
};

View File

@ -1,7 +1,7 @@
import { Button, Form, Modal } from 'antd';
import { FormInstance } from 'antd/lib';
import sendInvite from 'api/v1/invite/create';
import getPendingInvites from 'api/v1/invite/getPendingInvites';
import get from 'api/v1/invite/get';
import ROUTES from 'constants/routes';
import { useNotifications } from 'hooks/useNotifications';
import { useAppContext } from 'providers/App/App';
@ -14,7 +14,9 @@ import {
} from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { PayloadProps } from 'types/api/user/getPendingInvites';
import { SuccessResponseV2 } from 'types/api';
import APIError from 'types/api/error';
import { PendingInvite } from 'types/api/user/getPendingInvites';
import { ROLES } from 'types/roles';
import InviteTeamMembers from '../InviteTeamMembers';
@ -31,6 +33,7 @@ export interface InviteUserModalProps {
interface DataProps {
key: number;
name: string;
id: string;
email: string;
accessLevel: ROLES;
inviteLink: string;
@ -50,17 +53,21 @@ function InviteUserModal(props: InviteUserModalProps): JSX.Element {
const [isInvitingMembers, setIsInvitingMembers] = useState<boolean>(false);
const [modalForm] = Form.useForm<InviteMemberFormValues>(form);
const getPendingInvitesResponse = useQuery({
queryFn: getPendingInvites,
const getPendingInvitesResponse = useQuery<
SuccessResponseV2<PendingInvite[]>,
APIError
>({
queryFn: get,
queryKey: ['getPendingInvites', user?.accessJwt],
enabled: shouldCallApi,
});
const getParsedInviteData = useCallback(
(payload: PayloadProps = []) =>
(payload: PendingInvite[] = []) =>
payload?.map((data) => ({
key: data.createdAt,
name: data?.name,
id: data.id,
email: data.email,
accessLevel: data.role,
inviteLink: `${window.location.origin}${ROUTES.SIGN_UP}?token=${data.token}`,
@ -71,16 +78,16 @@ function InviteUserModal(props: InviteUserModalProps): JSX.Element {
useEffect(() => {
if (
getPendingInvitesResponse.status === 'success' &&
getPendingInvitesResponse?.data?.payload
getPendingInvitesResponse?.data?.data
) {
const data = getParsedInviteData(
getPendingInvitesResponse?.data?.payload || [],
getPendingInvitesResponse?.data?.data || [],
);
setDataSource?.(data);
}
}, [
getParsedInviteData,
getPendingInvitesResponse?.data?.payload,
getPendingInvitesResponse?.data?.data,
getPendingInvitesResponse.status,
setDataSource,
]);
@ -91,33 +98,30 @@ function InviteUserModal(props: InviteUserModalProps): JSX.Element {
setIsInvitingMembers?.(true);
values?.members?.forEach(
async (member): Promise<void> => {
const { error, statusCode } = await sendInvite({
try {
await sendInvite({
email: member.email,
name: member?.name,
role: member.role,
frontendBaseUrl: window.location.origin,
});
if (statusCode !== 200) {
notifications.error({
message:
error ||
t('something_went_wrong', {
ns: 'common',
}),
});
} else if (statusCode === 200) {
notifications.success({
message: 'Invite sent successfully',
});
} catch (error) {
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
}
},
);
setTimeout(async () => {
const { data, status } = await getPendingInvitesResponse.refetch();
if (status === 'success' && data.payload) {
setDataSource?.(getParsedInviteData(data?.payload || []));
if (status === 'success' && data.data) {
setDataSource?.(getParsedInviteData(data?.data || []));
}
setIsInvitingMembers?.(false);
toggleModal(false);

View File

@ -1,9 +1,8 @@
import { Button, Modal, Space, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import getOrgUser from 'api/v1/user/getOrgUser';
import getAll from 'api/v1/user/get';
import deleteUser from 'api/v1/user/id/delete';
import editUserApi from 'api/v1/user/id/update';
import updateRole from 'api/v1/user/id/updateRole';
import update from 'api/v1/user/id/update';
import { ResizeTable } from 'components/ResizeTable';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import dayjs from 'dayjs';
@ -12,6 +11,9 @@ import { useAppContext } from 'providers/App/App';
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { SuccessResponseV2 } from 'types/api';
import APIError from 'types/api/error';
import { UserResponse } from 'types/api/user/getUsers';
import { ROLES } from 'types/roles';
import DeleteMembersDetails from '../DeleteMembersDetails';
@ -84,11 +86,9 @@ function UserFunction({
const onDeleteHandler = async (): Promise<void> => {
try {
setIsDeleteLoading(true);
const response = await deleteUser({
await deleteUser({
userId: id,
});
if (response.statusCode === 200) {
onDelete();
notifications.success({
message: t('success', {
@ -96,67 +96,35 @@ function UserFunction({
}),
});
setIsDeleteModalVisible(false);
} else {
notifications.error({
message:
response.error ||
t('something_went_wrong', {
ns: 'common',
}),
});
}
setIsDeleteLoading(false);
} catch (error) {
setIsDeleteLoading(false);
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
}
};
const onInviteMemberHandler = async (): Promise<void> => {
const onEditMemberDetails = async (): Promise<void> => {
try {
setIsUpdateLoading(true);
const [editUserResponse, updateRoleResponse] = await Promise.all([
editUserApi({
await update({
userId: id,
name: updatedName,
}),
updateRole({
group_name: role,
userId: id,
}),
]);
if (
editUserResponse.statusCode === 200 &&
updateRoleResponse.statusCode === 200
) {
displayName: updatedName,
role,
});
onUpdateDetailsHandler();
notifications.success({
message: t('success', {
ns: 'common',
}),
});
} else {
notifications.error({
message:
editUserResponse.error ||
updateRoleResponse.error ||
t('something_went_wrong', {
ns: 'common',
}),
});
}
setIsUpdateLoading(false);
} catch (error) {
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
setIsUpdateLoading(false);
}
@ -193,7 +161,7 @@ function UserFunction({
</Button>,
<Button
key="Invite_team_members"
onClick={onInviteMemberHandler}
onClick={onEditMemberDetails}
type="primary"
disabled={isUpdateLoading}
loading={isUpdateLoading}
@ -230,28 +198,28 @@ function UserFunction({
function Members(): JSX.Element {
const { org } = useAppContext();
const { status, data, isLoading } = useQuery({
queryFn: () =>
getOrgUser({
orgId: (org || [])[0].id,
}),
const { status, data, isLoading } = useQuery<
SuccessResponseV2<UserResponse[]>,
APIError
>({
queryFn: () => getAll(),
queryKey: ['getOrgUser', org?.[0].id],
});
const [dataSource, setDataSource] = useState<DataType[]>([]);
useEffect(() => {
if (status === 'success' && data?.payload && Array.isArray(data.payload)) {
const updatedData: DataType[] = data?.payload?.map((e) => ({
if (status === 'success' && data?.data && Array.isArray(data.data)) {
const updatedData: DataType[] = data?.data?.map((e) => ({
accessLevel: e.role,
email: e.email,
id: String(e.id),
joinedOn: String(e.createdAt),
name: e.name,
name: e.displayName,
}));
setDataSource(updatedData);
}
}, [data?.payload, status]);
}, [data?.data, status]);
const columns: ColumnsType<DataType> = [
{

View File

@ -1,7 +1,7 @@
import { PlusOutlined } from '@ant-design/icons';
import { Button, Form, Space, Typography } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import getPendingInvites from 'api/v1/invite/getPendingInvites';
import get from 'api/v1/invite/get';
import deleteInvite from 'api/v1/invite/id/delete';
import { ResizeTable } from 'components/ResizeTable';
import { INVITE_MEMBERS_HASH } from 'constants/app';
@ -13,7 +13,9 @@ import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { PayloadProps } from 'types/api/user/getPendingInvites';
import { SuccessResponseV2 } from 'types/api';
import APIError from 'types/api/error';
import { PendingInvite } from 'types/api/user/getPendingInvites';
import { ROLES } from 'types/roles';
import InviteUserModal from '../InviteUserModal/InviteUserModal';
@ -46,8 +48,11 @@ function PendingInvitesContainer(): JSX.Element {
}
}, [state.error, state.value, t, notifications]);
const getPendingInvitesResponse = useQuery({
queryFn: getPendingInvites,
const getPendingInvitesResponse = useQuery<
SuccessResponseV2<PendingInvite[]>,
APIError
>({
queryFn: get,
queryKey: ['getPendingInvites', user?.accessJwt],
});
@ -66,10 +71,11 @@ function PendingInvitesContainer(): JSX.Element {
const { hash } = useLocation();
const getParsedInviteData = useCallback(
(payload: PayloadProps = []) =>
(payload: PendingInvite[] = []) =>
payload?.map((data) => ({
key: data.createdAt,
name: data.name,
id: data.id,
email: data.email,
accessLevel: data.role,
inviteLink: `${window.location.origin}${ROUTES.SIGN_UP}?token=${data.token}`,
@ -86,28 +92,26 @@ function PendingInvitesContainer(): JSX.Element {
useEffect(() => {
if (
getPendingInvitesResponse.status === 'success' &&
getPendingInvitesResponse?.data?.payload
getPendingInvitesResponse?.data?.data
) {
const data = getParsedInviteData(
getPendingInvitesResponse?.data?.payload || [],
getPendingInvitesResponse?.data?.data || [],
);
setDataSource(data);
}
}, [
getParsedInviteData,
getPendingInvitesResponse?.data?.payload,
getPendingInvitesResponse?.data?.data,
getPendingInvitesResponse.status,
]);
const onRevokeHandler = async (email: string): Promise<void> => {
const onRevokeHandler = async (id: string): Promise<void> => {
try {
const response = await deleteInvite({
email,
await deleteInvite({
id,
});
if (response.statusCode === 200) {
// remove from the client data
const index = dataSource.findIndex((e) => e.email === email);
const index = dataSource.findIndex((e) => e.id === id);
if (index !== -1) {
setDataSource([
...dataSource.slice(0, index),
@ -119,20 +123,10 @@ function PendingInvitesContainer(): JSX.Element {
ns: 'common',
}),
});
} else {
notifications.error({
message:
response.error ||
t('something_went_wrong', {
ns: 'common',
}),
});
}
} catch (error) {
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
}
};
@ -170,9 +164,7 @@ function PendingInvitesContainer(): JSX.Element {
key: 'Action',
render: (_, record): JSX.Element => (
<Space direction="horizontal">
<Typography.Link
onClick={(): Promise<void> => onRevokeHandler(record.email)}
>
<Typography.Link onClick={(): Promise<void> => onRevokeHandler(record.id)}>
Revoke
</Typography.Link>
<Typography.Link
@ -242,6 +234,7 @@ export interface InviteTeamMembersProps {
interface DataProps {
key: number;
name: string;
id: string;
email: string;
accessLevel: ROLES;
inviteLink: string;

View File

@ -38,7 +38,7 @@ function AddNewPipeline({
id: v4(),
orderId: (currPipelineData?.length || 0) + 1,
createdAt: new Date().toISOString(),
createdBy: user?.name || '',
createdBy: user?.displayName || '',
name: values.name,
alias: values.name.replace(/\s/g, ''),
description: values.description,

View File

@ -10,6 +10,7 @@ import { Label } from 'pages/SignUp/styles';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-use';
import APIError from 'types/api/error';
import { ButtonContainer, FormContainer, FormWrapper } from './styles';
@ -43,35 +44,24 @@ function ResetPassword({ version }: ResetPasswordProps): JSX.Element {
setLoading(true);
const { password } = form.getFieldsValue();
const response = await resetPasswordApi({
await resetPasswordApi({
password,
token: token || '',
});
if (response.statusCode === 200) {
notifications.success({
message: t('success', {
ns: 'common',
}),
});
history.push(ROUTES.LOGIN);
} else {
notifications.error({
message:
response.error ||
t('something_went_wrong', {
ns: 'common',
}),
});
}
setLoading(false);
} catch (error) {
setLoading(false);
notifications.error({
message: t('something_went_wrong', {
ns: 'common',
}),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
}
};

View File

@ -69,7 +69,7 @@ function SideNav(): JSX.Element {
const userSettingsMenuItem = {
key: ROUTES.MY_SETTINGS,
label: user?.name || 'User',
label: user?.displayName || 'User',
icon: <UserCircle size={16} />,
};

View File

@ -15,9 +15,7 @@ export function useResizeObserver<T extends HTMLElement>(
height: ref.current?.clientHeight || 0,
});
// eslint-disable-next-line consistent-return
useEffect(() => {
if (ref.current) {
const handleResize = debounce((entries: ResizeObserverEntry[]) => {
const entry = entries[0];
if (entry) {
@ -27,12 +25,14 @@ export function useResizeObserver<T extends HTMLElement>(
}, debounceTime);
const ro = new ResizeObserver(handleResize);
ro.observe(ref.current);
const referenceNode = ref.current;
if (referenceNode) {
ro.observe(referenceNode);
}
return (): void => {
ro.disconnect();
if (referenceNode) ro.disconnect();
};
}
}, [ref, debounceTime]);
return size;

View File

@ -1,7 +1,7 @@
import getUser from 'api/v1/user/id/get';
import { useQuery, UseQueryResult } from 'react-query';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps } from 'types/api/user/getUser';
import { SuccessResponseV2 } from 'types/api';
import { UserResponse } from 'types/api/user/getUser';
const useGetUser = (userId: string, isLoggedIn: boolean): UseGetUser =>
useQuery({
@ -10,9 +10,6 @@ const useGetUser = (userId: string, isLoggedIn: boolean): UseGetUser =>
enabled: !!userId && !!isLoggedIn,
});
type UseGetUser = UseQueryResult<
SuccessResponse<PayloadProps> | ErrorResponse,
unknown
>;
type UseGetUser = UseQueryResult<SuccessResponseV2<UserResponse>, unknown>;
export default useGetUser;

View File

@ -34,8 +34,8 @@ export const handlers = [
res(ctx.status(200), ctx.json(topLevelOperationSuccessResponse)),
),
rest.get('http://localhost/api/v1/orgUsers/*', (req, res, ctx) =>
res(ctx.status(200), ctx.json(membersResponse)),
rest.get('http://localhost/api/v1/user', (req, res, ctx) =>
res(ctx.status(200), ctx.json({ status: '200', data: membersResponse })),
),
rest.get(
'http://localhost/api/v3/autocomplete/attribute_keys',

View File

@ -1,8 +1,9 @@
import { Button, Form, Input, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import accept from 'api/v1/invite/id/accept';
import getInviteDetails from 'api/v1/invite/id/get';
import loginApi from 'api/v1/login/login';
import signUpApi from 'api/v1/register/signup';
import loginApi from 'api/v1/user/login';
import afterLogin from 'AppRoutes/utils';
import WelcomeLeftContainer from 'components/WelcomeLeftContainer';
import ROUTES from 'constants/routes';
@ -12,6 +13,9 @@ import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
import { SuccessResponseV2 } from 'types/api';
import APIError from 'types/api/error';
import { InviteDetails } from 'types/api/user/getInviteDetails';
import { PayloadProps as LoginPrecheckPayloadProps } from 'types/api/user/loginPrecheck';
import { ButtonContainer, FormContainer, FormWrapper, Label } from './styles';
@ -49,7 +53,10 @@ function SignUp({ version }: SignUpProps): JSX.Element {
const token = params.get('token');
const [isDetailsDisable, setIsDetailsDisable] = useState<boolean>(false);
const getInviteDetailsResponse = useQuery({
const getInviteDetailsResponse = useQuery<
SuccessResponseV2<InviteDetails>,
APIError
>({
queryFn: () =>
getInviteDetails({
inviteId: token || '',
@ -64,9 +71,9 @@ function SignUp({ version }: SignUpProps): JSX.Element {
useEffect(() => {
if (
getInviteDetailsResponse.status === 'success' &&
getInviteDetailsResponse.data.payload
getInviteDetailsResponse.data.data
) {
const responseDetails = getInviteDetailsResponse.data.payload;
const responseDetails = getInviteDetailsResponse.data.data;
if (responseDetails.precheck) setPrecheck(responseDetails.precheck);
form.setFieldValue('firstName', responseDetails.name);
form.setFieldValue('email', responseDetails.email);
@ -82,7 +89,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
getInviteDetailsResponse.data?.payload,
getInviteDetailsResponse.data?.data,
form,
getInviteDetailsResponse.status,
]);
@ -90,22 +97,24 @@ function SignUp({ version }: SignUpProps): JSX.Element {
useEffect(() => {
if (
getInviteDetailsResponse.status === 'success' &&
getInviteDetailsResponse.data?.error
getInviteDetailsResponse?.error
) {
const { error } = getInviteDetailsResponse.data;
const { error } = getInviteDetailsResponse;
notifications.error({
message: error,
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
}
}, [
getInviteDetailsResponse,
getInviteDetailsResponse.data,
getInviteDetailsResponse.status,
notifications,
]);
const isPreferenceVisible = token === null;
const isSignUp = token === null;
const commonHandler = async (values: FormValues): Promise<void> => {
const signUp = async (values: FormValues): Promise<void> => {
try {
const { organizationName, password, firstName, email } = values;
const response = await signUpApi({
@ -122,22 +131,35 @@ function SignUp({ version }: SignUpProps): JSX.Element {
password,
});
if (loginResponse.statusCode === 200) {
const { payload } = loginResponse;
await afterLogin(payload.userId, payload.accessJwt, payload.refreshJwt);
} else {
notifications.error({
message: loginResponse.error || t('unexpected_error'),
});
}
} else {
notifications.error({
message: response.error || t('unexpected_error'),
});
const { data } = loginResponse;
await afterLogin(data.userId, data.accessJwt, data.refreshJwt);
}
} catch (error) {
notifications.error({
message: t('unexpected_error'),
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
}
};
const acceptInvite = async (values: FormValues): Promise<void> => {
try {
const { password, email, firstName } = values;
await accept({
password,
token: params.get('token') || '',
displayName: firstName,
});
const loginResponse = await loginApi({
email,
password,
});
const { data } = loginResponse;
await afterLogin(data.userId, data.accessJwt, data.refreshJwt);
} catch (error) {
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
}
};
@ -150,22 +172,16 @@ function SignUp({ version }: SignUpProps): JSX.Element {
return;
}
setLoading(true);
try {
const values = form.getFieldsValue();
const response = await signUpApi({
email: values.email,
name: values.firstName,
orgDisplayName: values.organizationName,
password: values.password,
token: params.get('token') || undefined,
const response = await accept({
password: '',
token: params.get('token') || '',
sourceUrl: encodeURIComponent(window.location.href),
});
if (response.statusCode === 200) {
if (response.payload?.sso) {
if (response.payload?.ssoUrl) {
window.location.href = response.payload?.ssoUrl;
if (response.data?.sso) {
if (response.data?.ssoUrl) {
window.location.href = response.data?.ssoUrl;
} else {
notifications.error({
message: t('failed_to_initiate_login'),
@ -174,11 +190,6 @@ function SignUp({ version }: SignUpProps): JSX.Element {
history.push(ROUTES.LOGIN);
}
}
} else {
notifications.error({
message: response.error || t('unexpected_error'),
});
}
} catch (error) {
notifications.error({
message: t('unexpected_error'),
@ -205,15 +216,14 @@ function SignUp({ version }: SignUpProps): JSX.Element {
return;
}
if (isPreferenceVisible) {
await commonHandler(values);
} else {
if (isSignUp) {
await signUp(values);
logEvent('Account Created Successfully', {
email: values.email,
name: values.firstName,
});
await commonHandler(values);
} else {
await acceptInvite(values);
}
setLoading(false);
@ -227,7 +237,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
};
const getIsNameVisible = (): boolean =>
!(form.getFieldValue('firstName') === 0 && !isPreferenceVisible);
!(form.getFieldValue('firstName') === 0 && !isSignUp);
const isNameVisible = getIsNameVisible();
@ -343,7 +353,7 @@ function SignUp({ version }: SignUpProps): JSX.Element {
)}
</div>
)}
{isPreferenceVisible && (
{isSignUp && (
<Typography.Paragraph
italic
style={{

View File

@ -71,10 +71,10 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
error: userFetchError,
} = useGetUser(user.id, isLoggedIn);
useEffect(() => {
if (!isFetchingUser && userData && userData.payload) {
if (!isFetchingUser && userData && userData.data) {
setUser((prev) => ({
...prev,
...userData.payload,
...userData.data,
}));
setOrg((prev) => {
if (!prev) {
@ -82,19 +82,19 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
return [
{
createdAt: 0,
id: userData.payload.orgId,
displayName: userData.payload.organization,
id: userData.data.orgId,
displayName: userData.data.organization,
},
];
}
// else mutate the existing entry
const orgIndex = prev.findIndex((e) => e.id === userData.payload.orgId);
const orgIndex = prev.findIndex((e) => e.id === userData.data.orgId);
const updatedOrg: Organization[] = [
...prev.slice(0, orgIndex),
{
createdAt: 0,
id: userData.payload.orgId,
displayName: userData.payload.organization,
id: userData.data.orgId,
displayName: userData.data.organization,
},
...prev.slice(orgIndex + 1, prev.length),
];

View File

@ -2,7 +2,7 @@ import { FeatureFlagProps as FeatureFlags } from 'types/api/features/getFeatures
import { PayloadProps as LicensesResModel } from 'types/api/licenses/getAll';
import { LicenseV3ResModel, TrialInfo } from 'types/api/licensesV3/getActive';
import { Organization } from 'types/api/user/getOrganization';
import { PayloadProps as User } from 'types/api/user/getUser';
import { UserResponse as User } from 'types/api/user/getUser';
import { OrgPreference } from 'types/reducer/app';
export interface IAppContext {

View File

@ -17,8 +17,7 @@ function getUserDefaults(): IUser {
refreshJwt,
id: userId,
email: '',
name: '',
profilePictureURL: '',
displayName: '',
createdAt: 0,
organization: '',
orgId: '',

View File

@ -1,7 +1,7 @@
import { apiV3 } from 'api/apiV1';
import getLocalStorageApi from 'api/browser/localstorage/get';
import { Logout } from 'api/utils';
import loginApi from 'api/v1/user/login';
import loginApi from 'api/v1/login/login';
import afterLogin from 'AppRoutes/utils';
import { ENVIRONMENT } from 'constants/env';
import { LIVE_TAIL_HEARTBEAT_TIMEOUT } from 'constants/liveTail';
@ -18,6 +18,7 @@ import {
useRef,
useState,
} from 'react';
import APIError from 'types/api/error';
interface IEventSourceContext {
eventSourceInstance: EventSourcePolyfill | null;
@ -80,34 +81,24 @@ export function EventSourceProvider({
const response = await loginApi({
refreshToken: getLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN) || '',
});
if (response.statusCode === 200) {
// Update tokens in local storage
afterLogin(
response.payload.userId,
response.payload.accessJwt,
response.payload.refreshJwt,
response.data.userId,
response.data.accessJwt,
response.data.refreshJwt,
true,
);
// If token refresh was successful, we'll let the component
// handle reconnection through the reconnectDueToError state
setReconnectDueToError(true);
setIsConnectionError(true);
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) {
// If there was an error during token refresh, we'll just
// let the component handle the error state
notifications.error({ message: 'Sorry, something went wrong' });
console.error('Error refreshing token:', error);
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
setIsConnectionError(true);
if (!eventSourceRef.current) return;
eventSourceRef.current.close();

View File

@ -145,8 +145,7 @@ export function getAppContextMock(
refreshJwt: 'some-refresh-token',
id: 'some-user-id',
email: 'does-not-matter@signoz.io',
name: 'John Doe',
profilePictureURL: '',
displayName: 'John Doe',
createdAt: 1732544623,
organization: 'Nightswatch',
orgId: 'does-not-matter-id',

View 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;
}

View File

@ -7,5 +7,6 @@ export interface Props {
}
export interface PayloadProps {
status: string;
data: string;
}

View File

@ -1,7 +1,5 @@
import { User } from 'types/reducer/app';
export interface Props {
email: User['email'];
id: string;
}
export interface PayloadProps {

View File

@ -6,4 +6,5 @@ export interface Props {
export interface PayloadProps {
data: string;
status: string;
}

View File

@ -1,4 +1,5 @@
import { User } from 'types/reducer/app';
import { ROLES } from 'types/roles';
import { PayloadProps as Payload } from './getUser';
@ -6,5 +7,6 @@ export type PayloadProps = Payload;
export interface Props {
userId: User['userId'];
name: User['name'];
displayName: User['displayName'];
role?: ROLES;
}

View File

@ -9,9 +9,14 @@ export interface Props {
}
export interface PayloadProps {
data: InviteDetails;
status: string;
}
export interface InviteDetails {
createdAt: number;
email: User['email'];
name: User['name'];
name: User['displayName'];
role: ROLES;
token: string;
organization: Organization['displayName'];

View File

@ -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[];

View File

@ -4,9 +4,13 @@ import { ROLES } from 'types/roles';
export interface PendingInvite {
createdAt: number;
email: User['email'];
name: User['name'];
name: User['displayName'];
role: ROLES;
id: string;
token: string;
}
export type PayloadProps = PendingInvite[];
export type PayloadProps = {
data: PendingInvite[];
status: string;
};

View File

@ -4,7 +4,12 @@ export interface Props {
userId: User['userId'];
}
export interface PayloadProps {
export interface GetResetPasswordToken {
token: string;
userId: string;
}
export interface PayloadProps {
data: GetResetPasswordToken;
status: string;
}

View File

@ -6,13 +6,16 @@ export interface Props {
token?: string;
}
export interface PayloadProps {
export interface UserResponse {
createdAt: number;
email: string;
id: string;
name: string;
displayName: string;
orgId: string;
profilePictureURL: string;
organization: string;
role: ROLES;
}
export interface PayloadProps {
data: UserResponse;
status: string;
}

View 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;
}

View File

@ -1,40 +1,12 @@
import { User } from 'types/reducer/app';
import { ErrorResponse } from '..';
export interface UserProps {
name: User['name'];
name: User['displayName'];
email: User['email'];
role: string;
frontendBaseUrl: string;
}
export interface UsersProps {
users: UserProps[];
}
export interface PayloadProps {
data: string;
}
export interface FailedInvite {
email: string;
error: string;
}
export interface SuccessfulInvite {
email: string;
invite_link: string;
status: string;
}
export interface InviteUsersResponse extends ErrorResponse {
status: string;
summary: {
total_invites: number;
successful_invites: number;
failed_invites: number;
};
successful_invites: SuccessfulInvite[];
failed_invites: FailedInvite[];
invites: UserProps[];
}

View File

@ -1,13 +1,18 @@
export interface PayloadProps {
data: UserLoginResponse;
status: string;
}
export interface Props {
email?: string;
password?: string;
refreshToken?: UserLoginResponse['refreshJwt'];
}
export interface UserLoginResponse {
accessJwt: string;
accessJwtExpiry: number;
refreshJwt: string;
refreshJwtExpiry: number;
userId: string;
}
export interface Props {
email?: string;
password?: string;
refreshToken?: PayloadProps['refreshJwt'];
}

View File

@ -5,4 +5,5 @@ export interface Props {
export interface PayloadProps {
data: string;
status: string;
}

View File

@ -2,7 +2,7 @@ import { User } from 'types/reducer/app';
import { ROLES } from 'types/roles';
export interface Props {
name: User['name'];
name: User['displayName'];
email: User['email'];
role: ROLES;
frontendBaseUrl: string;
@ -10,4 +10,5 @@ export interface Props {
export interface PayloadProps {
data: string;
status: string;
}

View File

@ -1,13 +1,12 @@
import { PayloadProps as ConfigPayload } from 'types/api/dynamicConfigs/getDynamicConfigs';
import { PayloadProps as UserPayload } from 'types/api/user/getUser';
import { UserResponse as UserPayload } from 'types/api/user/getUser';
export interface User {
accessJwt: string;
refreshJwt: string;
userId: string;
email: UserPayload['email'];
name: UserPayload['name'];
profilePictureURL: UserPayload['profilePictureURL'];
displayName: UserPayload['displayName'];
}
export interface OrgPreference {

View File

@ -3,6 +3,7 @@
"sourceMap": true,
"outDir": "./dist/",
"noImplicitAny": true,
"noImplicitReturns": true,
"module": "esnext",
"target": "es5",
"jsx": "react-jsx",