diff --git a/site/src/i18n/en/index.ts b/site/src/i18n/en/index.ts index 59674f9b7dbf6..d44bb2e202e0d 100644 --- a/site/src/i18n/en/index.ts +++ b/site/src/i18n/en/index.ts @@ -17,6 +17,7 @@ import appearanceSettings from "./appearanceSettings.json" import starterTemplatesPage from "./starterTemplatesPage.json" import starterTemplatePage from "./starterTemplatePage.json" import createTemplatePage from "./createTemplatePage.json" +import userSettingsPage from "./userSettingsPage.json" export const en = { common, @@ -38,4 +39,5 @@ export const en = { starterTemplatesPage, starterTemplatePage, createTemplatePage, + userSettingsPage, } diff --git a/site/src/i18n/en/userSettingsPage.json b/site/src/i18n/en/userSettingsPage.json new file mode 100644 index 0000000000000..c641239e6c2af --- /dev/null +++ b/site/src/i18n/en/userSettingsPage.json @@ -0,0 +1,3 @@ +{ + "securityUpdateSuccessMessage": "Updated password." +} diff --git a/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.test.tsx b/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.test.tsx index fb4dedb10eb1d..b61f44500fb59 100644 --- a/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.test.tsx +++ b/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.test.tsx @@ -3,7 +3,6 @@ import * as API from "../../../api/api" import { GlobalSnackbar } from "../../../components/GlobalSnackbar/GlobalSnackbar" import * as SecurityForm from "../../../components/SettingsSecurityForm/SettingsSecurityForm" import { renderWithAuth } from "../../../testHelpers/renderHelpers" -import * as AuthXService from "../../../xServices/auth/authXService" import { SecurityPage } from "./SecurityPage" import i18next from "i18next" @@ -47,9 +46,10 @@ describe("SecurityPage", () => { const { user } = renderPage() await fillAndSubmitForm() - const successMessage = await screen.findByText( - AuthXService.Language.successSecurityUpdate, - ) + const expectedMessage = t("securityUpdateSuccessMessage", { + ns: "userSettingsPage", + }) + const successMessage = await screen.findByText(expectedMessage) expect(successMessage).toBeDefined() expect(API.updateUserPassword).toBeCalledTimes(1) expect(API.updateUserPassword).toBeCalledWith(user.id, newData) diff --git a/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx b/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx index e9154276374ab..956789b945c5f 100644 --- a/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx +++ b/site/src/pages/UserSettingsPage/SecurityPage/SecurityPage.tsx @@ -1,30 +1,34 @@ -import { useActor } from "@xstate/react" -import React, { useContext } from "react" +import { useMachine } from "@xstate/react" +import { useMe } from "hooks/useMe" +import React from "react" +import { userSecuritySettingsMachine } from "xServices/userSecuritySettings/userSecuritySettingsXService" import { Section } from "../../../components/Section/Section" import { SecurityForm } from "../../../components/SettingsSecurityForm/SettingsSecurityForm" -import { XServiceContext } from "../../../xServices/StateContext" export const Language = { title: "Security", } export const SecurityPage: React.FC = () => { - const xServices = useContext(XServiceContext) - const [authState, authSend] = useActor(xServices.authXService) - const { me, updateSecurityError } = authState.context - - if (!me) { - throw new Error("No current user found") - } + const me = useMe() + const [securityState, securitySend] = useMachine( + userSecuritySettingsMachine, + { + context: { + userId: me.id, + }, + }, + ) + const { error } = securityState.context return (
{ - authSend({ + securitySend({ type: "UPDATE_SECURITY", data, }) diff --git a/site/src/xServices/auth/authXService.ts b/site/src/xServices/auth/authXService.ts index 9ff04e305b0c7..61e95d9edb35c 100644 --- a/site/src/xServices/auth/authXService.ts +++ b/site/src/xServices/auth/authXService.ts @@ -5,7 +5,6 @@ import { displaySuccess } from "../../components/GlobalSnackbar/utils" export const Language = { successProfileUpdate: "Updated settings.", - successSecurityUpdate: "Updated password.", successRegenerateSSHKey: "SSH Key regenerated successfully", } @@ -80,7 +79,6 @@ export interface AuthContext { getMethodsError?: Error | unknown authError?: Error | unknown updateProfileError?: Error | unknown - updateSecurityError?: Error | unknown me?: TypesGen.User methods?: TypesGen.AuthMethods permissions?: Permissions @@ -95,7 +93,6 @@ export type AuthEvent = | { type: "SIGN_OUT" } | { type: "SIGN_IN"; email: string; password: string } | { type: "UPDATE_PROFILE"; data: TypesGen.UpdateUserProfileRequest } - | { type: "UPDATE_SECURITY"; data: TypesGen.UpdateUserPasswordRequest } | { type: "GET_SSH_KEY" } | { type: "REGENERATE_SSH_KEY" } | { type: "CONFIRM_REGENERATE_SSH_KEY" } @@ -385,41 +382,6 @@ export const authMachine = }, }, }, - security: { - initial: "idle", - states: { - idle: { - initial: "noError", - states: { - noError: {}, - error: {}, - }, - on: { - UPDATE_SECURITY: { - target: "updatingSecurity", - }, - }, - }, - updatingSecurity: { - entry: "clearUpdateSecurityError", - invoke: { - src: "updateSecurity", - onDone: [ - { - actions: "notifySuccessSecurityUpdate", - target: "#authState.signedIn.security.idle.noError", - }, - ], - onError: [ - { - actions: "assignUpdateSecurityError", - target: "#authState.signedIn.security.idle.error", - }, - ], - }, - }, - }, - }, }, on: { SIGN_OUT: { @@ -513,13 +475,6 @@ export const authMachine = return API.updateProfile(context.me.id, event.data) }, - updateSecurity: async (context, event) => { - if (!context.me) { - throw new Error("No current user found") - } - - return API.updateUserPassword(context.me.id, event.data) - }, checkPermissions: async () => { return API.checkAuthorization({ checks: permissionsToCheck, @@ -572,15 +527,6 @@ export const authMachine = clearUpdateProfileError: assign({ updateProfileError: (_) => undefined, }), - clearUpdateSecurityError: assign({ - updateSecurityError: (_) => undefined, - }), - notifySuccessSecurityUpdate: () => { - displaySuccess(Language.successSecurityUpdate) - }, - assignUpdateSecurityError: assign({ - updateSecurityError: (_, event) => event.data, - }), assignPermissions: assign({ // Setting event.data as Permissions to be more stricted. So we know // what permissions we asked for. diff --git a/site/src/xServices/userSecuritySettings/userSecuritySettingsXService.ts b/site/src/xServices/userSecuritySettings/userSecuritySettingsXService.ts new file mode 100644 index 0000000000000..6cc8712542b7f --- /dev/null +++ b/site/src/xServices/userSecuritySettings/userSecuritySettingsXService.ts @@ -0,0 +1,71 @@ +import { assign, createMachine } from "xstate" +import * as API from "api/api" +import { UpdateUserPasswordRequest } from "api/typesGenerated" +import { displaySuccess } from "components/GlobalSnackbar/utils" +import { t } from "i18next" + +interface Context { + userId: string + error?: unknown +} + +type Events = { type: "UPDATE_SECURITY"; data: UpdateUserPasswordRequest } + +export const userSecuritySettingsMachine = createMachine( + { + id: "userSecuritySettings", + predictableActionArguments: true, + schema: { + context: {} as Context, + events: {} as Events, + }, + tsTypes: {} as import("./userSecuritySettingsXService.typegen").Typegen0, + initial: "idle", + states: { + idle: { + on: { + UPDATE_SECURITY: { + target: "updatingSecurity", + }, + }, + }, + updatingSecurity: { + entry: "clearError", + invoke: { + src: "updateSecurity", + onDone: [ + { + actions: "notifyUpdate", + target: "idle", + }, + ], + onError: [ + { + actions: "assignError", + target: "idle", + }, + ], + }, + }, + }, + }, + { + services: { + updateSecurity: async ({ userId }, { data }) => + API.updateUserPassword(userId, data), + }, + actions: { + clearError: assign({ + error: (_) => undefined, + }), + notifyUpdate: () => { + displaySuccess( + t("securityUpdateSuccessMessage", { ns: "userSettingsPage" }), + ) + }, + assignError: assign({ + error: (_, event) => event.data, + }), + }, + }, +)