684 lines
16 KiB
TypeScript
684 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.authStore.authDetails?.edition === 'ee' ||
|
|
this.account?.plan?.type === 'enterprise'
|
|
);
|
|
}
|
|
|
|
get isSSOSupported() {
|
|
return (
|
|
this.isEnterprise ||
|
|
this.account?.edition === 'msaas' ||
|
|
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;
|