From f40f9b0287c9f110dc710025157aec064083d3b1 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Mon, 8 Jul 2024 10:21:03 -0400 Subject: [PATCH] refactor(editor): Migrate `users.store` to composition API (no-changelog) (#9960) --- packages/editor-ui/src/Interface.ts | 7 - .../src/components/DeleteUserModal.vue | 2 +- .../src/components/MfaSetupModal.vue | 2 +- .../components/Projects/ProjectSettings.vue | 2 +- .../components/__tests__/BannersStack.test.ts | 2 +- .../__tests__/PersonalizationModal.spec.ts | 2 +- .../banners/__tests__/V1Banner.spec.ts | 5 +- .../src/stores/__tests__/posthog.test.ts | 2 +- packages/editor-ui/src/stores/users.store.ts | 739 ++++++++++-------- .../editor-ui/src/views/SettingsUsersView.vue | 12 +- .../__tests__/SettingsPersonalView.test.ts | 2 +- 11 files changed, 407 insertions(+), 370 deletions(-) diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index cc2db3e999..59bc77f2d9 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -1376,13 +1376,6 @@ export interface IVersionsState { currentVersion: IVersion | undefined; } -export interface IUsersState { - initialized: boolean; - currentUserId: null | string; - users: { [userId: string]: IUser }; - currentUserCloudInfo: Cloud.UserAccount | null; -} - export interface IWorkflowsState { currentWorkflowExecutions: ExecutionSummary[]; activeWorkflowExecution: ExecutionSummary | null; diff --git a/packages/editor-ui/src/components/DeleteUserModal.vue b/packages/editor-ui/src/components/DeleteUserModal.vue index 311392521c..7516466a34 100644 --- a/packages/editor-ui/src/components/DeleteUserModal.vue +++ b/packages/editor-ui/src/components/DeleteUserModal.vue @@ -119,7 +119,7 @@ export default defineComponent({ ...mapStores(useUsersStore, useProjectsStore), userToDelete(): IUser | null { if (!this.activeId) return null; - return this.usersStore.getUserById(this.activeId); + return this.usersStore.usersById[this.activeId]; }, isPending(): boolean { return this.userToDelete ? this.userToDelete && !this.userToDelete.firstName : false; diff --git a/packages/editor-ui/src/components/MfaSetupModal.vue b/packages/editor-ui/src/components/MfaSetupModal.vue index 78c9cf7f61..b9fd149c86 100644 --- a/packages/editor-ui/src/components/MfaSetupModal.vue +++ b/packages/editor-ui/src/components/MfaSetupModal.vue @@ -254,7 +254,7 @@ const onSetupClick = async () => { const getMfaQR = async () => { try { - const response = await userStore.getMfaQR(); + const response = await userStore.fetchMfaQR(); qrCode.value = response.qrCode; secret.value = response.secret; recoveryCodes.value = response.recoveryCodes; diff --git a/packages/editor-ui/src/components/Projects/ProjectSettings.vue b/packages/editor-ui/src/components/Projects/ProjectSettings.vue index 9451436d90..6002335fe8 100644 --- a/packages/editor-ui/src/components/Projects/ProjectSettings.vue +++ b/packages/editor-ui/src/components/Projects/ProjectSettings.vue @@ -69,7 +69,7 @@ const firstLicensedRole = computed(() => projectRoles.value.find((role) => role. const onAddMember = (userId: string) => { isDirty.value = true; - const user = usersStore.getUserById(userId); + const user = usersStore.usersById[userId]; if (!user) return; const { id, firstName, lastName, email } = user; diff --git a/packages/editor-ui/src/components/__tests__/BannersStack.test.ts b/packages/editor-ui/src/components/__tests__/BannersStack.test.ts index 309c2cc168..71955e8739 100644 --- a/packages/editor-ui/src/components/__tests__/BannersStack.test.ts +++ b/packages/editor-ui/src/components/__tests__/BannersStack.test.ts @@ -23,7 +23,7 @@ const initialState = { }, [STORES.USERS]: { currentUserId: 'aaa-bbb', - users: { + usersById: { 'aaa-bbb': { id: 'aaa-bbb', role: ROLE.Owner, diff --git a/packages/editor-ui/src/components/__tests__/PersonalizationModal.spec.ts b/packages/editor-ui/src/components/__tests__/PersonalizationModal.spec.ts index 188d6acf88..e325d6dec1 100644 --- a/packages/editor-ui/src/components/__tests__/PersonalizationModal.spec.ts +++ b/packages/editor-ui/src/components/__tests__/PersonalizationModal.spec.ts @@ -23,7 +23,7 @@ const pinia = createTestingPinia({ }, }, [STORES.USERS]: { - users: { + usersById: { 123: { email: 'john@doe.com', firstName: 'John', diff --git a/packages/editor-ui/src/components/banners/__tests__/V1Banner.spec.ts b/packages/editor-ui/src/components/banners/__tests__/V1Banner.spec.ts index b2e7ac6f4a..d9cbedd9e8 100644 --- a/packages/editor-ui/src/components/banners/__tests__/V1Banner.spec.ts +++ b/packages/editor-ui/src/components/banners/__tests__/V1Banner.spec.ts @@ -23,9 +23,8 @@ describe('V1 Banner', () => { }); it('should render banner with dismiss call if user is owner', () => { - vi.spyOn(usersStore, 'currentUser', 'get').mockReturnValue({ - role: ROLE.Owner, - } as IUser); + usersStore.usersById = { '1': { role: ROLE.Owner } as IUser }; + usersStore.currentUserId = '1'; const { container } = render(V1Banner); expect(container).toMatchSnapshot(); diff --git a/packages/editor-ui/src/stores/__tests__/posthog.test.ts b/packages/editor-ui/src/stores/__tests__/posthog.test.ts index e9eabebe3a..f06e053935 100644 --- a/packages/editor-ui/src/stores/__tests__/posthog.test.ts +++ b/packages/editor-ui/src/stores/__tests__/posthog.test.ts @@ -45,7 +45,7 @@ function setCurrentUser() { function resetStores() { useSettingsStore().$reset(); - useUsersStore().$reset(); + useUsersStore().reset(); } function setup() { diff --git a/packages/editor-ui/src/stores/users.store.ts b/packages/editor-ui/src/stores/users.store.ts index f91fd2e372..0c6963819c 100644 --- a/packages/editor-ui/src/stores/users.store.ts +++ b/packages/editor-ui/src/stores/users.store.ts @@ -1,32 +1,11 @@ import type { IUpdateUserSettingsReqPayload, UpdateGlobalRolePayload } from '@/api/users'; -import { - changePassword, - deleteUser, - getPasswordResetLink, - getUsers, - login, - loginCurrentUser, - logout, - sendForgotPasswordEmail, - setupOwner, - submitPersonalizationSurvey, - updateCurrentUser, - updateCurrentUserPassword, - updateCurrentUserSettings, - updateOtherUserSettings, - validatePasswordToken, - validateSignupToken, - updateGlobalRole, -} from '@/api/users'; +import * as usersApi from '@/api/users'; import { PERSONALIZATION_MODAL_KEY, STORES, ROLE } from '@/constants'; import type { Cloud, - IInviteResponse, IPersonalizationLatestVersion, - IRole, IUser, IUserResponse, - IUsersState, CurrentUserResponse, InvitableRoleName, } from '@/Interface'; @@ -37,344 +16,410 @@ import { usePostHog } from './posthog.store'; import { useSettingsStore } from './settings.store'; import { useUIStore } from './ui.store'; import { useCloudPlanStore } from './cloudPlan.store'; -import { disableMfa, enableMfa, getMfaQR, verifyMfaToken } from '@/api/mfa'; -import { confirmEmail, getCloudUserInfo } from '@/api/cloudPlans'; +import * as mfaApi from '@/api/mfa'; +import * as cloudApi from '@/api/cloudPlans'; import { useRBACStore } from '@/stores/rbac.store'; import type { Scope } from '@n8n/permissions'; -import { inviteUsers, acceptInvitation } from '@/api/invitation'; +import * as invitationsApi from '@/api/invitation'; import { useNpsSurveyStore } from './npsSurvey.store'; +import { computed, ref } from 'vue'; -const isPendingUser = (user: IUserResponse | null) => !!user?.isPending; -const isInstanceOwner = (user: IUserResponse | null) => user?.role === ROLE.Owner; -const isDefaultUser = (user: IUserResponse | null) => isInstanceOwner(user) && isPendingUser(user); +const _isPendingUser = (user: IUserResponse | null) => !!user?.isPending; +const _isInstanceOwner = (user: IUserResponse | null) => user?.role === ROLE.Owner; +const _isDefaultUser = (user: IUserResponse | null) => + _isInstanceOwner(user) && _isPendingUser(user); -export const useUsersStore = defineStore(STORES.USERS, { - state: (): IUsersState => ({ - initialized: false, - currentUserId: null, - users: {}, - currentUserCloudInfo: null, - }), - getters: { - allUsers(): IUser[] { - return Object.values(this.users); - }, - userActivated(): boolean { - return Boolean(this.currentUser?.settings?.userActivated); - }, - currentUser(): IUser | null { - return this.currentUserId ? this.users[this.currentUserId] : null; - }, - isDefaultUser(): boolean { - return isDefaultUser(this.currentUser); - }, - isInstanceOwner(): boolean { - return isInstanceOwner(this.currentUser); - }, - mfaEnabled(): boolean { - return this.currentUser?.mfaEnabled ?? false; - }, - getUserById(state) { - return (userId: string): IUser | null => state.users[userId]; - }, - globalRoleName(): IRole { - return this.currentUser?.role ?? 'default'; - }, - personalizedNodeTypes(): string[] { - const user = this.currentUser; - if (!user) { - return []; - } +export const useUsersStore = defineStore(STORES.USERS, () => { + const initialized = ref(false); + const currentUserId = ref(null); + const usersById = ref>({}); + const currentUserCloudInfo = ref(null); - const answers = user.personalizationAnswers; - if (!answers) { - return []; - } - return getPersonalizedNodeTypes(answers); - }, - }, - actions: { - async initialize() { - if (this.initialized) { - return; - } + // Stores - try { - await this.loginWithCookie(); - this.initialized = true; - } catch (e) {} - }, - setCurrentUser(user: CurrentUserResponse) { - this.addUsers([user]); - this.currentUserId = user.id; + const RBACStore = useRBACStore(); + const npsSurveyStore = useNpsSurveyStore(); + const uiStore = useUIStore(); + const rootStore = useRootStore(); + const settingsStore = useSettingsStore(); + const cloudPlanStore = useCloudPlanStore(); - const defaultScopes: Scope[] = []; - useRBACStore().setGlobalScopes(user.globalScopes || defaultScopes); - usePostHog().init(user.featureFlags); - useNpsSurveyStore().setupNpsSurveyOnLogin(user.id, user.settings); - }, - unsetCurrentUser() { - this.currentUserId = null; - this.currentUserCloudInfo = null; - useRBACStore().setGlobalScopes([]); - }, - addUsers(users: IUserResponse[]) { - users.forEach((userResponse: IUserResponse) => { - const prevUser = this.users[userResponse.id] || {}; - const updatedUser = { - ...prevUser, - ...userResponse, - }; - const user: IUser = { - ...updatedUser, - fullName: userResponse.firstName - ? `${updatedUser.firstName} ${updatedUser.lastName || ''}` - : undefined, - isDefaultUser: isDefaultUser(updatedUser), - isPendingUser: isPendingUser(updatedUser), - }; + // Composables - this.users = { - ...this.users, - [user.id]: user, - }; - }); - }, - deleteUserById(userId: string): void { - const { [userId]: _, ...users } = this.users; - this.users = users; - }, - setPersonalizationAnswers(answers: IPersonalizationLatestVersion): void { - if (!this.currentUser) { - return; - } + const postHogStore = usePostHog(); - this.users = { - ...this.users, - [this.currentUser.id]: { - ...this.currentUser, - personalizationAnswers: answers, - }, + // Computed + + const allUsers = computed(() => Object.values(usersById.value)); + + const currentUser = computed(() => + currentUserId.value ? usersById.value[currentUserId.value] : null, + ); + + const userActivated = computed(() => Boolean(currentUser.value?.settings?.userActivated)); + + const isDefaultUser = computed(() => _isDefaultUser(currentUser.value)); + + const isInstanceOwner = computed(() => _isInstanceOwner(currentUser.value)); + + const mfaEnabled = computed(() => currentUser.value?.mfaEnabled ?? false); + + const globalRoleName = computed(() => currentUser.value?.role ?? 'default'); + + const personalizedNodeTypes = computed(() => { + const user = currentUser.value; + if (!user) { + return []; + } + + const answers = user.personalizationAnswers; + if (!answers) { + return []; + } + return getPersonalizedNodeTypes(answers); + }); + + // Methods + + const addUsers = (newUsers: IUserResponse[]) => { + newUsers.forEach((userResponse: IUserResponse) => { + const prevUser = usersById.value[userResponse.id] || {}; + const updatedUser = { + ...prevUser, + ...userResponse, + }; + const user: IUser = { + ...updatedUser, + fullName: userResponse.firstName + ? `${updatedUser.firstName} ${updatedUser.lastName || ''}` + : undefined, + isDefaultUser: _isDefaultUser(updatedUser), + isPendingUser: _isPendingUser(updatedUser), }; - }, - async loginWithCookie(): Promise { - const rootStore = useRootStore(); - const user = await loginCurrentUser(rootStore.restApiContext); - if (!user) { - return; - } - this.setCurrentUser(user); - }, - async loginWithCreds(params: { - email: string; - password: string; - mfaToken?: string; - mfaRecoveryCode?: string; - }): Promise { - const rootStore = useRootStore(); - const user = await login(rootStore.restApiContext, params); - if (!user) { - return; - } + usersById.value = { + ...usersById.value, + [user.id]: user, + }; + }); + }; - this.setCurrentUser(user); - }, - async logout(): Promise { - const rootStore = useRootStore(); - await logout(rootStore.restApiContext); - this.unsetCurrentUser(); - useCloudPlanStore().reset(); - usePostHog().reset(); - useUIStore().clearBannerStack(); - useNpsSurveyStore().resetNpsSurveyOnLogOut(); - }, - async createOwner(params: { - firstName: string; - lastName: string; - email: string; - password: string; - }): Promise { - const rootStore = useRootStore(); - const user = await setupOwner(rootStore.restApiContext, params); - const settingsStore = useSettingsStore(); - if (user) { - this.setCurrentUser(user); - settingsStore.stopShowingSetupPage(); - } - }, - async validateSignupToken(params: { - inviteeId: string; - inviterId: string; - }): Promise<{ inviter: { firstName: string; lastName: string } }> { - const rootStore = useRootStore(); - return await validateSignupToken(rootStore.restApiContext, params); - }, - async acceptInvitation(params: { - inviteeId: string; - inviterId: string; - firstName: string; - lastName: string; - password: string; - }): Promise { - const rootStore = useRootStore(); - const user = await acceptInvitation(rootStore.restApiContext, params); - if (user) { - this.setCurrentUser(user); - } - }, - async sendForgotPasswordEmail(params: { email: string }): Promise { - const rootStore = useRootStore(); - await sendForgotPasswordEmail(rootStore.restApiContext, params); - }, - async validatePasswordToken(params: { token: string }): Promise { - const rootStore = useRootStore(); - await validatePasswordToken(rootStore.restApiContext, params); - }, - async changePassword(params: { - token: string; - password: string; - mfaToken?: string; - }): Promise { - const rootStore = useRootStore(); - await changePassword(rootStore.restApiContext, params); - }, - async updateUser(params: { - id: string; - firstName: string; - lastName: string; - email: string; - }): Promise { - const rootStore = useRootStore(); - const user = await updateCurrentUser(rootStore.restApiContext, params); - this.addUsers([user]); - }, - async updateUserSettings(settings: IUpdateUserSettingsReqPayload): Promise { - const rootStore = useRootStore(); - const updatedSettings = await updateCurrentUserSettings(rootStore.restApiContext, settings); - if (this.currentUser) { - this.currentUser.settings = updatedSettings; - this.addUsers([this.currentUser]); - } - }, - async updateOtherUserSettings( - userId: string, - settings: IUpdateUserSettingsReqPayload, - ): Promise { - const rootStore = useRootStore(); - const updatedSettings = await updateOtherUserSettings( - rootStore.restApiContext, - userId, - settings, - ); - this.users[userId].settings = updatedSettings; - this.addUsers([this.users[userId]]); - }, - async updateCurrentUserPassword({ - password, + const setCurrentUser = (user: CurrentUserResponse) => { + addUsers([user]); + currentUserId.value = user.id; + + const defaultScopes: Scope[] = []; + RBACStore.setGlobalScopes(user.globalScopes || defaultScopes); + postHogStore.init(user.featureFlags); + npsSurveyStore.setupNpsSurveyOnLogin(user.id, user.settings); + }; + + const loginWithCookie = async () => { + const user = await usersApi.loginCurrentUser(rootStore.restApiContext); + if (!user) { + return; + } + + setCurrentUser(user); + }; + + const initialize = async () => { + if (initialized.value) { + return; + } + + try { + await loginWithCookie(); + initialized.value = true; + } catch (e) {} + }; + + const unsetCurrentUser = () => { + currentUserId.value = null; + currentUserCloudInfo.value = null; + RBACStore.setGlobalScopes([]); + }; + + const deleteUserById = (userId: string) => { + const { [userId]: _, ...rest } = usersById.value; + usersById.value = rest; + }; + + const setPersonalizationAnswers = (answers: IPersonalizationLatestVersion) => { + if (!currentUser.value) { + return; + } + + usersById.value = { + ...usersById.value, + [currentUser.value.id]: { + ...currentUser.value, + personalizationAnswers: answers, + }, + }; + }; + + const loginWithCreds = async (params: { + email: string; + password: string; + mfaToken?: string; + mfaRecoveryCode?: string; + }) => { + const user = await usersApi.login(rootStore.restApiContext, params); + if (!user) { + return; + } + + setCurrentUser(user); + }; + + const logout = async () => { + await usersApi.logout(rootStore.restApiContext); + unsetCurrentUser(); + cloudPlanStore.reset(); + postHogStore.reset(); + uiStore.clearBannerStack(); + npsSurveyStore.resetNpsSurveyOnLogOut(); + }; + + const createOwner = async (params: { + firstName: string; + lastName: string; + email: string; + password: string; + }) => { + const user = await usersApi.setupOwner(rootStore.restApiContext, params); + if (user) { + setCurrentUser(user); + settingsStore.stopShowingSetupPage(); + } + }; + + const validateSignupToken = async (params: { inviteeId: string; inviterId: string }) => { + return await usersApi.validateSignupToken(rootStore.restApiContext, params); + }; + + const acceptInvitation = async (params: { + inviteeId: string; + inviterId: string; + firstName: string; + lastName: string; + password: string; + }) => { + const user = await invitationsApi.acceptInvitation(rootStore.restApiContext, params); + if (user) { + setCurrentUser(user); + } + }; + + const sendForgotPasswordEmail = async (params: { email: string }) => { + await usersApi.sendForgotPasswordEmail(rootStore.restApiContext, params); + }; + + const validatePasswordToken = async (params: { token: string }) => { + await usersApi.validatePasswordToken(rootStore.restApiContext, params); + }; + + const changePassword = async (params: { token: string; password: string; mfaToken?: string }) => { + await usersApi.changePassword(rootStore.restApiContext, params); + }; + + const updateUser = async (params: { + id: string; + firstName: string; + lastName: string; + email: string; + }) => { + const user = await usersApi.updateCurrentUser(rootStore.restApiContext, params); + addUsers([user]); + }; + + const updateUserSettings = async (settings: IUpdateUserSettingsReqPayload) => { + const updatedSettings = await usersApi.updateCurrentUserSettings( + rootStore.restApiContext, + settings, + ); + if (currentUser.value) { + currentUser.value.settings = updatedSettings; + addUsers([currentUser.value]); + } + }; + + const updateOtherUserSettings = async ( + userId: string, + settings: IUpdateUserSettingsReqPayload, + ) => { + const updatedSettings = await usersApi.updateOtherUserSettings( + rootStore.restApiContext, + userId, + settings, + ); + usersById.value[userId].settings = updatedSettings; + addUsers([usersById.value[userId]]); + }; + + const updateCurrentUserPassword = async ({ + password, + currentPassword, + }: { + password: string; + currentPassword: string; + }) => { + await usersApi.updateCurrentUserPassword(rootStore.restApiContext, { + newPassword: password, currentPassword, - }: { - password: string; - currentPassword: string; - }): Promise { - const rootStore = useRootStore(); - await updateCurrentUserPassword(rootStore.restApiContext, { - newPassword: password, - currentPassword, - }); - }, - async deleteUser(params: { id: string; transferId?: string }): Promise { - const rootStore = useRootStore(); - await deleteUser(rootStore.restApiContext, params); - this.deleteUserById(params.id); - }, - async fetchUsers(): Promise { - const rootStore = useRootStore(); - const users = await getUsers(rootStore.restApiContext); - this.addUsers(users); - }, - async inviteUsers( - params: Array<{ email: string; role: InvitableRoleName }>, - ): Promise { - const rootStore = useRootStore(); - const users = await inviteUsers(rootStore.restApiContext, params); - this.addUsers( - users.map(({ user }, index) => ({ - isPending: true, - globalRole: { name: params[index].role }, - ...user, - })), - ); - return users; - }, - async reinviteUser({ email, role }: { email: string; role: InvitableRoleName }): Promise { - const rootStore = useRootStore(); - const invitationResponse = await inviteUsers(rootStore.restApiContext, [{ email, role }]); - if (!invitationResponse[0].user.emailSent) { - throw Error(invitationResponse[0].error); - } - }, - async getUserPasswordResetLink(params: { id: string }): Promise<{ link: string }> { - const rootStore = useRootStore(); - return await getPasswordResetLink(rootStore.restApiContext, params); - }, - async submitPersonalizationSurvey(results: IPersonalizationLatestVersion): Promise { - const rootStore = useRootStore(); - await submitPersonalizationSurvey(rootStore.restApiContext, results); - this.setPersonalizationAnswers(results); - }, - async showPersonalizationSurvey(): Promise { - const settingsStore = useSettingsStore(); - const surveyEnabled = settingsStore.isPersonalizationSurveyEnabled; - const currentUser = this.currentUser; - if (surveyEnabled && currentUser && !currentUser.personalizationAnswers) { - const uiStore = useUIStore(); - uiStore.openModal(PERSONALIZATION_MODAL_KEY); - } - }, - async getMfaQR(): Promise<{ qrCode: string; secret: string; recoveryCodes: string[] }> { - const rootStore = useRootStore(); - return await getMfaQR(rootStore.restApiContext); - }, - async verifyMfaToken(data: { token: string }): Promise { - const rootStore = useRootStore(); - return await verifyMfaToken(rootStore.restApiContext, data); - }, - async enableMfa(data: { token: string }) { - const rootStore = useRootStore(); - const usersStore = useUsersStore(); - await enableMfa(rootStore.restApiContext, data); - const currentUser = usersStore.currentUser; - if (currentUser) { - currentUser.mfaEnabled = true; - } - }, - async disabledMfa() { - const rootStore = useRootStore(); - const usersStore = useUsersStore(); - await disableMfa(rootStore.restApiContext); - const currentUser = usersStore.currentUser; - if (currentUser) { - currentUser.mfaEnabled = false; - } - }, - async fetchUserCloudAccount() { - let cloudUser: Cloud.UserAccount | null = null; - try { - cloudUser = await getCloudUserInfo(useRootStore().restApiContext); - this.currentUserCloudInfo = cloudUser; - } catch (error) { - throw new Error(error); - } - }, - async confirmEmail() { - await confirmEmail(useRootStore().restApiContext); - }, + }); + }; - async updateGlobalRole({ id, newRoleName }: UpdateGlobalRolePayload) { - const rootStore = useRootStore(); - await updateGlobalRole(rootStore.restApiContext, { id, newRoleName }); - await this.fetchUsers(); - }, - }, + const deleteUser = async (params: { id: string; transferId?: string }) => { + await usersApi.deleteUser(rootStore.restApiContext, params); + deleteUserById(params.id); + }; + + const fetchUsers = async () => { + const users = await usersApi.getUsers(rootStore.restApiContext); + addUsers(users); + }; + + const inviteUsers = async (params: Array<{ email: string; role: InvitableRoleName }>) => { + const invitedUsers = await invitationsApi.inviteUsers(rootStore.restApiContext, params); + addUsers( + invitedUsers.map(({ user }, index) => ({ + isPending: true, + globalRole: { name: params[index].role }, + ...user, + })), + ); + return invitedUsers; + }; + + const reinviteUser = async ({ email, role }: { email: string; role: InvitableRoleName }) => { + const invitationResponse = await invitationsApi.inviteUsers(rootStore.restApiContext, [ + { email, role }, + ]); + if (!invitationResponse[0].user.emailSent) { + throw Error(invitationResponse[0].error); + } + }; + + const getUserPasswordResetLink = async (params: { id: string }) => { + return await usersApi.getPasswordResetLink(rootStore.restApiContext, params); + }; + + const submitPersonalizationSurvey = async (results: IPersonalizationLatestVersion) => { + await usersApi.submitPersonalizationSurvey(rootStore.restApiContext, results); + setPersonalizationAnswers(results); + }; + + const showPersonalizationSurvey = async () => { + const surveyEnabled = settingsStore.isPersonalizationSurveyEnabled; + if (surveyEnabled && currentUser.value && !currentUser.value.personalizationAnswers) { + uiStore.openModal(PERSONALIZATION_MODAL_KEY); + } + }; + + const fetchMfaQR = async () => { + return await mfaApi.getMfaQR(rootStore.restApiContext); + }; + + const verifyMfaToken = async (data: { token: string }) => { + return await mfaApi.verifyMfaToken(rootStore.restApiContext, data); + }; + + const enableMfa = async (data: { token: string }) => { + await mfaApi.enableMfa(rootStore.restApiContext, data); + if (currentUser.value) { + currentUser.value.mfaEnabled = true; + } + }; + + const disableMfa = async () => { + await mfaApi.disableMfa(rootStore.restApiContext); + if (currentUser.value) { + currentUser.value.mfaEnabled = false; + } + }; + + const disabledMfa = async () => { + await mfaApi.disableMfa(rootStore.restApiContext); + if (currentUser.value) { + currentUser.value.mfaEnabled = false; + } + }; + + const fetchUserCloudAccount = async () => { + let cloudUser: Cloud.UserAccount | null = null; + try { + cloudUser = await cloudApi.getCloudUserInfo(rootStore.restApiContext); + currentUserCloudInfo.value = cloudUser; + } catch (error) { + throw new Error(error); + } + }; + + const confirmEmail = async () => { + await cloudApi.confirmEmail(rootStore.restApiContext); + }; + + const updateGlobalRole = async ({ id, newRoleName }: UpdateGlobalRolePayload) => { + await usersApi.updateGlobalRole(rootStore.restApiContext, { id, newRoleName }); + await fetchUsers(); + }; + + const reset = () => { + initialized.value = false; + currentUserId.value = null; + usersById.value = {}; + currentUserCloudInfo.value = null; + }; + + return { + initialized, + currentUserId, + usersById, + currentUserCloudInfo, + allUsers, + currentUser, + userActivated, + isDefaultUser, + isInstanceOwner, + mfaEnabled, + globalRoleName, + personalizedNodeTypes, + addUsers, + setCurrentUser, + loginWithCookie, + initialize, + unsetCurrentUser, + deleteUserById, + setPersonalizationAnswers, + loginWithCreds, + logout, + createOwner, + validateSignupToken, + acceptInvitation, + sendForgotPasswordEmail, + validatePasswordToken, + changePassword, + updateUser, + updateUserSettings, + updateOtherUserSettings, + updateCurrentUserPassword, + deleteUser, + fetchUsers, + inviteUsers, + reinviteUser, + getUserPasswordResetLink, + submitPersonalizationSurvey, + showPersonalizationSurvey, + fetchMfaQR, + verifyMfaToken, + enableMfa, + disableMfa, + fetchUserCloudAccount, + confirmEmail, + updateGlobalRole, + disabledMfa, + reset, + }; }); diff --git a/packages/editor-ui/src/views/SettingsUsersView.vue b/packages/editor-ui/src/views/SettingsUsersView.vue index 8df0e92a84..f865b17cd2 100644 --- a/packages/editor-ui/src/views/SettingsUsersView.vue +++ b/packages/editor-ui/src/views/SettingsUsersView.vue @@ -216,13 +216,13 @@ export default defineComponent({ this.uiStore.openModal(INVITE_USER_MODAL_KEY); }, async onDelete(userId: string) { - const user = this.usersStore.getUserById(userId); + const user = this.usersStore.usersById[userId]; if (user) { this.uiStore.openDeleteUserModal(userId); } }, async onReinvite(userId: string) { - const user = this.usersStore.getUserById(userId); + const user = this.usersStore.usersById[userId]; if (user?.email && user?.role) { if (!['global:admin', 'global:member'].includes(user.role)) { throw new Error('Invalid role name on reinvite'); @@ -245,7 +245,7 @@ export default defineComponent({ } }, async onCopyInviteLink(userId: string) { - const user = this.usersStore.getUserById(userId); + const user = this.usersStore.usersById[userId]; if (user?.inviteAcceptUrl) { void this.clipboard.copy(user.inviteAcceptUrl); @@ -257,7 +257,7 @@ export default defineComponent({ } }, async onCopyPasswordResetLink(userId: string) { - const user = this.usersStore.getUserById(userId); + const user = this.usersStore.usersById[userId]; if (user) { const url = await this.usersStore.getUserPasswordResetLink(user); void this.clipboard.copy(url.link); @@ -270,7 +270,7 @@ export default defineComponent({ } }, async onAllowSSOManualLogin(userId: string) { - const user = this.usersStore.getUserById(userId); + const user = this.usersStore.usersById[userId]; if (user) { if (!user.settings) { user.settings = {}; @@ -286,7 +286,7 @@ export default defineComponent({ } }, async onDisallowSSOManualLogin(userId: string) { - const user = this.usersStore.getUserById(userId); + const user = this.usersStore.usersById[userId]; if (user?.settings) { user.settings.allowSSOManualLogin = false; await this.usersStore.updateOtherUserSettings(userId, user.settings); diff --git a/packages/editor-ui/src/views/__tests__/SettingsPersonalView.test.ts b/packages/editor-ui/src/views/__tests__/SettingsPersonalView.test.ts index c30f63fe1c..e62812e906 100644 --- a/packages/editor-ui/src/views/__tests__/SettingsPersonalView.test.ts +++ b/packages/editor-ui/src/views/__tests__/SettingsPersonalView.test.ts @@ -41,7 +41,7 @@ describe('SettingsPersonalView', () => { usersStore = useUsersStore(pinia); uiStore = useUIStore(pinia); - usersStore.users[currentUser.id] = currentUser; + usersStore.usersById[currentUser.id] = currentUser; usersStore.currentUserId = currentUser.id; await settingsStore.getSettings();