openreplay/frontend/app/mstore/userStore.ts
2025-02-11 16:37:25 +01:00

668 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';
class UserStore {
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) {
makeAutoObservable(this);
this.authStore = authStore;
void makePersistable(
this,
{
name: 'UserStore',
properties: [
'siteId',
'tenants',
'jwt',
'spotJwt',
'scopeState',
'onboarding',
{
key: 'account',
serialize: (acc) => {
return acc.id ? JSON.stringify(acc.toData()) : JSON.stringify({});
},
deserialize: (json) => {
return new Account(JSON.parse(json));
}
}
],
storage: window.localStorage
},
{
delay: 200
}
);
}
get isEnterprise() {
return (
this.account?.edition === 'ee' ||
this.authStore.authDetails?.edition === 'ee'
);
}
get isLoggedIn() {
return Boolean(this.jwt);
}
fetchLimits = (): Promise<any> => {
return 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> => {
return 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('User created successfully');
} else {
this.updateUser(newUser);
toast.success('User updated successfully');
}
});
resolve(response);
})
.catch(async (e) => {
const err = await e.response?.json();
runInAction(() => {
this.saving = false;
});
const errStr = err.errors[0]
? err.errors[0].includes('already exists')
? `This email is already linked to an account or team on OpenReplay and can't be used again.`
: err.errors[0]
: 'Error saving user';
toast.error(errStr);
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('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('Invite code copied successfully');
} else {
toast.error('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: 'Generating an invite code...',
success: '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) {
runInAction(() => {
this.signUpRequest = {
loading: false,
errors: error.response?.errors || []
};
});
toast.error('Error signing up; please check your data and try again');
} 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 (error) {
toast.error('Error resetting your password; please try again');
return error.response;
} 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(`Successfully changed password`);
return data;
} catch (e: any) {
toast.error(e.message || '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 = () => {
return 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);
export default userStore;