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 // 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',
{}, {},

View File

@ -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,
]); ]);

View File

@ -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 || '',
}); });
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

View File

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

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

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 { 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(),
}); });
} }
}; };

View File

@ -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,
}),
}); });
} }
}; };

View File

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

View File

@ -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&apos;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} />}

View File

@ -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&apos;ll help you get the most out of SigNoz, whether you&apos;re new to 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 { 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&apos;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} />

View File

@ -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(),
}),
}); });
} }
}; };

View File

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

View File

@ -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> = [
{ {

View File

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

View File

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

View File

@ -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(),
}),
}); });
} }
}; };

View File

@ -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} />,
}; };

View File

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

View File

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

View File

@ -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',

View File

@ -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={{

View File

@ -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),
]; ];

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 { 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 {

View File

@ -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: '',

View File

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

View File

@ -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',

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 { export interface PayloadProps {
status: string;
data: string; data: string;
} }

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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 { 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[];
} }

View File

@ -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'];
}

View File

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

View File

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

View File

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

View File

@ -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",