Replace complex error parsing with direct error message display. This improves code maintainability while still providing useful feedback to users when saving their account data fails.
691 lines
16 KiB
TypeScript
691 lines
16 KiB
TypeScript
import Account from 'Types/account';
|
|
import Client from 'Types/client';
|
|
import copy from 'copy-to-clipboard';
|
|
import { makeAutoObservable, runInAction } from 'mobx';
|
|
import { makePersistable } from 'mobx-persist-store';
|
|
import { toast } from 'react-toastify';
|
|
|
|
import { userService } from 'App/services';
|
|
import { deleteCookie } from 'App/utils';
|
|
|
|
import User from 'App/mstore/types/user';
|
|
import i18next, { TFunction } from 'i18next';
|
|
|
|
class UserStore {
|
|
t: TFunction;
|
|
|
|
list: User[] = [];
|
|
|
|
instance: User | null = null;
|
|
|
|
page: number = 1;
|
|
|
|
pageSize: number = 10;
|
|
|
|
searchQuery: string = '';
|
|
|
|
modifiedCount: number = 0;
|
|
|
|
loading: boolean = false;
|
|
|
|
saving: boolean = false;
|
|
|
|
limits: any = {};
|
|
|
|
initialDataFetched: boolean = false;
|
|
|
|
account = new Account();
|
|
|
|
siteId: string | null = null;
|
|
|
|
passwordRequestError: boolean = false;
|
|
|
|
passwordErrors: string[] = [];
|
|
|
|
tenants: any[] = [];
|
|
|
|
onboarding: boolean = false;
|
|
|
|
sites: any[] = [];
|
|
|
|
jwt: string | null = null;
|
|
|
|
spotJwt: string | null = null;
|
|
|
|
errors: any[] = [];
|
|
|
|
loginRequest = {
|
|
loading: false,
|
|
errors: [] as string[],
|
|
};
|
|
|
|
fetchInfoRequest = {
|
|
loading: false,
|
|
errors: [] as string[],
|
|
};
|
|
|
|
signUpRequest = {
|
|
loading: false,
|
|
errors: [] as string[],
|
|
};
|
|
|
|
updatePasswordRequest = {
|
|
loading: false,
|
|
errors: [] as string[],
|
|
};
|
|
|
|
scopeState: number | null = null;
|
|
|
|
client = new Client();
|
|
|
|
authStore: AuthStore;
|
|
|
|
constructor(authStore: AuthStore, t: TFunction) {
|
|
this.t = t;
|
|
makeAutoObservable(this);
|
|
this.authStore = authStore;
|
|
|
|
void makePersistable(
|
|
this,
|
|
{
|
|
name: 'UserStore',
|
|
properties: [
|
|
'siteId',
|
|
'tenants',
|
|
'jwt',
|
|
'spotJwt',
|
|
'scopeState',
|
|
'onboarding',
|
|
{
|
|
key: 'account',
|
|
serialize: (acc) =>
|
|
acc.id ? JSON.stringify(acc.toData()) : JSON.stringify({}),
|
|
deserialize: (json) => new Account(JSON.parse(json)),
|
|
},
|
|
],
|
|
storage: window.localStorage,
|
|
},
|
|
{
|
|
delay: 200,
|
|
},
|
|
);
|
|
}
|
|
|
|
get isEnterprise() {
|
|
return (
|
|
this.account?.edition === 'ee' ||
|
|
this.account?.edition === 'msaas' ||
|
|
this.authStore.authDetails?.edition === 'ee' ||
|
|
this.authStore.authDetails?.edition === 'msaas'
|
|
);
|
|
}
|
|
|
|
get isLoggedIn() {
|
|
return Boolean(this.jwt);
|
|
}
|
|
|
|
fetchLimits = (): Promise<any> =>
|
|
new Promise((resolve, reject) => {
|
|
userService
|
|
.getLimits()
|
|
.then((response: any) => {
|
|
runInAction(() => {
|
|
this.setLimits(response);
|
|
});
|
|
resolve(response);
|
|
})
|
|
.catch((error: any) => {
|
|
reject(error);
|
|
});
|
|
});
|
|
|
|
setLimits = (limits: any) => {
|
|
this.limits = limits;
|
|
};
|
|
|
|
initUser = (user?: User): Promise<void> =>
|
|
new Promise((resolve) => {
|
|
if (user) {
|
|
this.instance = new User().fromJson(user.toJson());
|
|
} else {
|
|
this.instance = new User();
|
|
}
|
|
resolve();
|
|
});
|
|
|
|
updateKey = (key: keyof this, value: any) => {
|
|
// @ts-ignore
|
|
this[key] = value;
|
|
|
|
if (key === 'searchQuery') {
|
|
this.page = 1;
|
|
}
|
|
};
|
|
|
|
updateUser = (user: User) => {
|
|
const index = this.list.findIndex((u) => u.userId === user.userId);
|
|
if (index > -1) {
|
|
this.list[index] = user;
|
|
}
|
|
};
|
|
|
|
fetchUser = (userId: string): Promise<any> => {
|
|
this.loading = true;
|
|
return new Promise((resolve, reject) => {
|
|
userService
|
|
.one(userId)
|
|
.then((data) => {
|
|
runInAction(() => {
|
|
this.instance = new User().fromJson(data);
|
|
});
|
|
resolve(data);
|
|
})
|
|
.catch((error) => {
|
|
runInAction(() => {
|
|
this.loading = false;
|
|
});
|
|
reject(error);
|
|
})
|
|
.finally(() => {
|
|
runInAction(() => {
|
|
this.loading = false;
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
fetchUsers = (): Promise<any> => {
|
|
this.loading = true;
|
|
return new Promise((resolve, reject) => {
|
|
userService
|
|
.all()
|
|
.then((response) => {
|
|
runInAction(() => {
|
|
this.list = response.map((user: any) => new User().fromJson(user));
|
|
});
|
|
resolve(response);
|
|
})
|
|
.catch((error) => {
|
|
runInAction(() => {
|
|
this.loading = false;
|
|
});
|
|
reject(error);
|
|
})
|
|
.finally(() => {
|
|
runInAction(() => {
|
|
this.loading = false;
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
saveUser = (user: User): Promise<any> => {
|
|
this.saving = true;
|
|
const wasCreating = !user.userId;
|
|
return new Promise((resolve, reject) => {
|
|
userService
|
|
.save(user)
|
|
.then((response) => {
|
|
runInAction(() => {
|
|
const newUser = new User().fromJson(response);
|
|
if (wasCreating) {
|
|
this.modifiedCount -= 1;
|
|
this.list.push(newUser);
|
|
toast.success(this.t('User created successfully'));
|
|
} else {
|
|
this.updateUser(newUser);
|
|
toast.success(this.t('User updated successfully'));
|
|
}
|
|
});
|
|
resolve(response);
|
|
})
|
|
.catch(async (e) => {
|
|
toast.error(e.message || this.t("Failed to save user's data."));
|
|
reject(e);
|
|
})
|
|
.finally(() => {
|
|
runInAction(() => {
|
|
this.saving = false;
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
deleteUser = (userId: string): Promise<any> => {
|
|
this.saving = true;
|
|
return new Promise((resolve, reject) => {
|
|
userService
|
|
.delete(userId)
|
|
.then((response) => {
|
|
runInAction(() => {
|
|
this.modifiedCount += 1;
|
|
this.list = this.list.filter((user) => user.userId !== userId);
|
|
});
|
|
resolve(response);
|
|
})
|
|
.catch((error) => {
|
|
runInAction(() => {
|
|
this.saving = false;
|
|
});
|
|
toast.error(this.t('Error deleting user'));
|
|
reject(error);
|
|
})
|
|
.finally(() => {
|
|
runInAction(() => {
|
|
this.saving = false;
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
copyInviteCode = (userId: string): void => {
|
|
const content = this.list.find((u) => u.userId === userId)?.invitationLink;
|
|
if (content) {
|
|
copy(content);
|
|
toast.success(this.t('Invite code copied successfully'));
|
|
} else {
|
|
toast.error(this.t('Invite code not found'));
|
|
}
|
|
};
|
|
|
|
generateInviteCode = (userId: string): Promise<any> => {
|
|
this.saving = true;
|
|
const promise = new Promise((resolve, reject) => {
|
|
userService
|
|
.generateInviteCode(userId)
|
|
.then((response) => {
|
|
runInAction(() => {
|
|
const index = this.list.findIndex((u) => u.userId === userId);
|
|
if (index > -1) {
|
|
this.list[index].updateKey('isExpiredInvite', false);
|
|
this.list[index].updateKey(
|
|
'invitationLink',
|
|
response.invitationLink,
|
|
);
|
|
}
|
|
copy(response.invitationLink);
|
|
});
|
|
resolve(response);
|
|
})
|
|
.catch((error) => {
|
|
runInAction(() => {
|
|
this.saving = false;
|
|
});
|
|
reject(error);
|
|
})
|
|
.finally(() => {
|
|
runInAction(() => {
|
|
this.saving = false;
|
|
});
|
|
});
|
|
});
|
|
|
|
toast.promise(promise, {
|
|
pending: this.t('Generating an invite code...'),
|
|
success: this.t('Invite code generated successfully'),
|
|
});
|
|
|
|
return promise;
|
|
};
|
|
|
|
syntheticLogin = ({ data }: any) => {
|
|
this.account = new Account(data.user);
|
|
this.spotJwt = data.spotJwt;
|
|
this.scopeState = data.scopeState;
|
|
this.loginRequest = { loading: false, errors: [] };
|
|
};
|
|
|
|
syntheticLoginError = (errors: any) => {
|
|
deleteCookie('jwt', '/', 'openreplay.com');
|
|
this.loginRequest = {
|
|
loading: false,
|
|
errors: errors || [],
|
|
};
|
|
};
|
|
|
|
login = async (params: any) => {
|
|
this.loginRequest = { loading: true, errors: [] };
|
|
try {
|
|
const data = await userService.login(params);
|
|
runInAction(() => {
|
|
this.account = new Account(data.user);
|
|
this.jwt = data.jwt;
|
|
this.spotJwt = data.spotJwt;
|
|
this.scopeState = data.scopeState;
|
|
this.loginRequest = { loading: false, errors: [] };
|
|
});
|
|
} catch (error: any) {
|
|
runInAction(() => {
|
|
console.log('Login failed', error);
|
|
deleteCookie('jwt', '/', 'openreplay.com');
|
|
this.loginRequest = {
|
|
loading: false,
|
|
errors: error.errors || [],
|
|
};
|
|
});
|
|
}
|
|
};
|
|
|
|
signup = async (params: any) => {
|
|
runInAction(() => {
|
|
this.signUpRequest = { loading: true, errors: [] };
|
|
});
|
|
try {
|
|
const data = await userService.signup(params);
|
|
runInAction(() => {
|
|
this.account = new Account(data.user);
|
|
this.scopeState = data.scopeState;
|
|
this.spotJwt = data.spotJwt;
|
|
this.jwt = data.jwt;
|
|
this.signUpRequest = { loading: false, errors: [] };
|
|
});
|
|
} catch (error) {
|
|
const inUse = error.message.includes('already in use');
|
|
const inUseMsg = this.t('An account with this email already exists. Please log in or use a different email address.')
|
|
const genericMsg = this.t('Error signing up; please check your data and try again')
|
|
runInAction(() => {
|
|
this.signUpRequest = {
|
|
loading: false,
|
|
errors: error.response?.errors || [],
|
|
};
|
|
});
|
|
toast.error(inUse ? inUseMsg : genericMsg);
|
|
} finally {
|
|
runInAction(() => {
|
|
this.signUpRequest.loading = false;
|
|
});
|
|
}
|
|
};
|
|
|
|
resetPassword = async (params: any) => {
|
|
runInAction(() => {
|
|
this.loading = true;
|
|
});
|
|
try {
|
|
const data = await userService.resetPassword(params);
|
|
runInAction(() => {
|
|
this.account = new Account(data.user);
|
|
this.jwt = data.jwt;
|
|
this.spotJwt = data.spotJwt;
|
|
});
|
|
} catch (e) {
|
|
toast.error(e.message || this.t('Error resetting your password; please try again'));
|
|
throw e;
|
|
} finally {
|
|
runInAction(() => {
|
|
this.loading = false;
|
|
});
|
|
}
|
|
};
|
|
|
|
requestResetPassword = async (params: any) => {
|
|
this.loading = true;
|
|
try {
|
|
await userService.requestResetPassword(params);
|
|
} catch (error) {
|
|
throw error;
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
};
|
|
|
|
updatePassword = async (params: any) => {
|
|
runInAction(() => {
|
|
this.updatePasswordRequest = { loading: true, errors: [] };
|
|
});
|
|
try {
|
|
const data = (await userService.updatePassword(params)) as {
|
|
jwt: string;
|
|
spotJwt: string;
|
|
data: any;
|
|
};
|
|
runInAction(() => {
|
|
this.jwt = data.jwt;
|
|
this.spotJwt = data.spotJwt;
|
|
this.scopeState = data.data.scopeState;
|
|
this.updatePasswordRequest = { loading: false, errors: [] };
|
|
});
|
|
toast.success(this.t('Successfully changed password'));
|
|
return data;
|
|
} catch (e: any) {
|
|
toast.error(e.message || this.t('Failed to updated password.'));
|
|
throw e;
|
|
} finally {
|
|
runInAction(() => {
|
|
this.updatePasswordRequest = {
|
|
loading: false,
|
|
errors: [],
|
|
};
|
|
});
|
|
}
|
|
};
|
|
|
|
fetchUserInfo = async () => {
|
|
runInAction(() => {
|
|
this.fetchInfoRequest = { loading: true, errors: [] };
|
|
});
|
|
try {
|
|
const data = await userService.fetchUserInfo();
|
|
runInAction(() => {
|
|
this.account = new Account(data);
|
|
this.scopeState = data.scopeState;
|
|
this.passwordErrors = [];
|
|
});
|
|
} catch (error) {
|
|
runInAction(() => {
|
|
deleteCookie('jwt', '/', 'openreplay.com');
|
|
this.resetStore();
|
|
});
|
|
} finally {
|
|
runInAction(() => {
|
|
this.fetchInfoRequest = { loading: false, errors: [] };
|
|
});
|
|
}
|
|
};
|
|
|
|
logout = async () => {
|
|
try {
|
|
await userService.logout();
|
|
runInAction(() => {
|
|
deleteCookie('jwt', '/', 'openreplay.com');
|
|
this.resetStore();
|
|
});
|
|
} catch (error) {
|
|
// TODO error handling
|
|
}
|
|
};
|
|
|
|
updateClient = async (params: any) => {
|
|
runInAction(() => {
|
|
this.loading = true;
|
|
});
|
|
try {
|
|
await userService.updateClient(params);
|
|
runInAction(() => {
|
|
Object.keys(params).forEach((key) => {
|
|
this.client[key] = params[key];
|
|
this.account[key] = params[key];
|
|
});
|
|
});
|
|
} catch (error) {
|
|
throw error;
|
|
} finally {
|
|
runInAction(() => {
|
|
this.loading = false;
|
|
});
|
|
}
|
|
};
|
|
|
|
updateJwt = ({ jwt, spotJwt }: { jwt?: string; spotJwt?: string }) => {
|
|
this.jwt = jwt ?? null;
|
|
this.spotJwt = spotJwt ?? null;
|
|
};
|
|
|
|
getJwt = () => this.jwt;
|
|
|
|
updateAccount = async (params: any) => {
|
|
runInAction(() => {
|
|
this.loading = true;
|
|
});
|
|
try {
|
|
const data = await userService.updateAccount(params);
|
|
runInAction(() => {
|
|
this.account = new Account(data);
|
|
this.scopeState = data.scopeState;
|
|
this.passwordErrors = [];
|
|
});
|
|
} catch (error) {
|
|
// TODO error handling
|
|
} finally {
|
|
runInAction(() => {
|
|
this.loading = false;
|
|
});
|
|
}
|
|
};
|
|
|
|
resendEmailVerification = async (email: string) => {
|
|
try {
|
|
await userService.resendEmailVerification(email);
|
|
} catch (error) {
|
|
// TODO error handling
|
|
}
|
|
};
|
|
|
|
pushNewSite = (newSite: any) => {
|
|
this.sites.push(newSite);
|
|
};
|
|
|
|
setOnboarding = (state: boolean = false) => {
|
|
this.onboarding = state;
|
|
};
|
|
|
|
resetErrors = () => {
|
|
this.passwordErrors = [];
|
|
this.loginRequest.errors = [];
|
|
this.errors = [];
|
|
};
|
|
|
|
updateModule = (moduleKey: string) => {
|
|
const modules = this.account.settings?.modules || [];
|
|
if (modules.includes(moduleKey)) {
|
|
this.account.settings.modules = modules.filter(
|
|
(module: string) => module !== moduleKey,
|
|
);
|
|
} else {
|
|
this.account.settings.modules = [...modules, moduleKey];
|
|
}
|
|
};
|
|
|
|
upgradeScope = async () => {
|
|
try {
|
|
await userService.changeScope(2);
|
|
runInAction(() => {
|
|
this.scopeState = 2;
|
|
});
|
|
} catch (error) {
|
|
// TODO error handling
|
|
}
|
|
};
|
|
|
|
downgradeScope = async () => {
|
|
try {
|
|
await userService.changeScope(1);
|
|
runInAction(() => {
|
|
this.scopeState = 1;
|
|
});
|
|
} catch (error) {
|
|
// TODO error handling
|
|
}
|
|
};
|
|
|
|
resetStore = async () => {
|
|
this.account = new Account();
|
|
this.siteId = null;
|
|
this.passwordRequestError = false;
|
|
this.passwordErrors = [];
|
|
this.tenants = [];
|
|
this.onboarding = false;
|
|
this.sites = [];
|
|
this.jwt = null;
|
|
this.spotJwt = null;
|
|
this.errors = [];
|
|
this.loginRequest = {
|
|
loading: false,
|
|
errors: [],
|
|
};
|
|
this.scopeState = null;
|
|
this.client = new Client();
|
|
this.list = [];
|
|
this.instance = null;
|
|
this.page = 1;
|
|
this.pageSize = 10;
|
|
this.searchQuery = '';
|
|
this.modifiedCount = 0;
|
|
this.loading = false;
|
|
this.saving = false;
|
|
this.limits = {};
|
|
this.initialDataFetched = false;
|
|
};
|
|
}
|
|
|
|
type AuthDetails = {
|
|
tenants: boolean;
|
|
sso: string | null;
|
|
ssoProvider: string | null;
|
|
enforceSSO: boolean | null;
|
|
edition?: 'foss' | 'ee' | 'msaas';
|
|
};
|
|
|
|
class AuthStore {
|
|
authDetails: AuthDetails = {
|
|
tenants: false,
|
|
sso: null,
|
|
ssoProvider: null,
|
|
enforceSSO: null,
|
|
edition: 'foss',
|
|
};
|
|
|
|
constructor() {
|
|
makeAutoObservable(this);
|
|
|
|
void makePersistable(this, {
|
|
name: 'AuthStore',
|
|
properties: [
|
|
'authDetails',
|
|
{
|
|
key: 'authDetails',
|
|
serialize: (ad) => {
|
|
// delete ad.edition;
|
|
return Object.keys(ad).length > 0
|
|
? JSON.stringify(ad)
|
|
: JSON.stringify({});
|
|
},
|
|
deserialize: (json) => {
|
|
const ad = JSON.parse(json);
|
|
// delete ad.edition;
|
|
return ad;
|
|
},
|
|
},
|
|
],
|
|
expireIn: 60000 * 60,
|
|
removeOnExpiration: true,
|
|
storage: window.localStorage,
|
|
});
|
|
}
|
|
|
|
fetchTenants = async () => {
|
|
try {
|
|
const response = await userService.fetchTenants();
|
|
runInAction(() => {
|
|
this.authDetails = response;
|
|
});
|
|
} catch (error) {
|
|
// TODO error handling
|
|
}
|
|
};
|
|
}
|
|
|
|
export const authStore = new AuthStore();
|
|
const userStore = new UserStore(authStore, i18next.t);
|
|
|
|
export default userStore;
|