diff --git a/site/src/i18n/en/userSettingsPage.json b/site/src/i18n/en/userSettingsPage.json index c641239e6c2af..c616d8caed09e 100644 --- a/site/src/i18n/en/userSettingsPage.json +++ b/site/src/i18n/en/userSettingsPage.json @@ -1,3 +1,4 @@ { - "securityUpdateSuccessMessage": "Updated password." + "securityUpdateSuccessMessage": "Updated password.", + "sshRegenerateSuccessMessage": "SSH Key regenerated successfully." } diff --git a/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx b/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx index 272c37056494b..9114f5d6defc0 100644 --- a/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx +++ b/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx @@ -5,9 +5,11 @@ import { MockGitSSHKey, renderWithAuth, } from "../../../testHelpers/renderHelpers" -import { Language as authXServiceLanguage } from "../../../xServices/auth/authXService" import { Language as SSHKeysPageLanguage, SSHKeysPage } from "./SSHKeysPage" import { Language as SSHKeysPageViewLanguage } from "./SSHKeysPageView" +import { i18n } from "i18n" + +const { t } = i18n describe("SSH keys Page", () => { it("shows the SSH key", async () => { @@ -52,7 +54,10 @@ describe("SSH keys Page", () => { fireEvent.click(confirmButton) // Check if the success message is displayed - await screen.findByText(authXServiceLanguage.successRegenerateSSHKey) + const successMessage = t("sshRegenerateSuccessMessage", { + ns: "userSettingsPage", + }) + await screen.findByText(successMessage) // Check if the API was called correctly expect(API.regenerateUserSSHKey).toBeCalledTimes(1) diff --git a/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.tsx b/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.tsx index 7e7133c44ea1c..7feceb30c47e4 100644 --- a/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.tsx +++ b/site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.tsx @@ -1,8 +1,8 @@ -import { useActor } from "@xstate/react" -import { useContext, useEffect, PropsWithChildren, FC } from "react" +import { useMachine } from "@xstate/react" +import { PropsWithChildren, FC } from "react" +import { sshKeyMachine } from "xServices/sshKey/sshKeyXService" import { ConfirmDialog } from "../../../components/Dialogs/ConfirmDialog/ConfirmDialog" import { Section } from "../../../components/SettingsLayout/Section" -import { XServiceContext } from "../../../xServices/StateContext" import { SSHKeysPageView } from "./SSHKeysPageView" export const Language = { @@ -15,19 +15,13 @@ export const Language = { } export const SSHKeysPage: FC> = () => { - const xServices = useContext(XServiceContext) - const [authState, authSend] = useActor(xServices.authXService) - const { sshKey, getSSHKeyError, regenerateSSHKeyError } = authState.context - - useEffect(() => { - authSend({ type: "GET_SSH_KEY" }) - }, [authSend]) - - const isLoading = authState.matches("signedIn.ssh.gettingSSHKey") - const hasLoaded = authState.matches("signedIn.ssh.loaded") + const [sshState, sshSend] = useMachine(sshKeyMachine) + const isLoading = sshState.matches("gettingSSHKey") + const hasLoaded = sshState.matches("loaded") + const { getSSHKeyError, regenerateSSHKeyError, sshKey } = sshState.context const onRegenerateClick = () => { - authSend({ type: "REGENERATE_SSH_KEY" }) + sshSend({ type: "REGENERATE_SSH_KEY" }) } return ( @@ -46,17 +40,15 @@ export const SSHKeysPage: FC> = () => { { - authSend({ type: "CONFIRM_REGENERATE_SSH_KEY" }) + sshSend({ type: "CONFIRM_REGENERATE_SSH_KEY" }) }} onClose={() => { - authSend({ type: "CANCEL_REGENERATE_SSH_KEY" }) + sshSend({ type: "CANCEL_REGENERATE_SSH_KEY" }) }} description={<>{Language.regenerateDialogMessage}} /> diff --git a/site/src/xServices/auth/authXService.ts b/site/src/xServices/auth/authXService.ts index 61e95d9edb35c..5ea51e6e7c056 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.", - successRegenerateSSHKey: "SSH Key regenerated successfully", } export const checks = { @@ -83,20 +82,12 @@ export interface AuthContext { methods?: TypesGen.AuthMethods permissions?: Permissions checkPermissionsError?: Error | unknown - // SSH - sshKey?: TypesGen.GitSSHKey - getSSHKeyError?: Error | unknown - regenerateSSHKeyError?: Error | unknown } export type AuthEvent = | { type: "SIGN_OUT" } | { type: "SIGN_IN"; email: string; password: string } | { type: "UPDATE_PROFILE"; data: TypesGen.UpdateUserProfileRequest } - | { type: "GET_SSH_KEY" } - | { type: "REGENERATE_SSH_KEY" } - | { type: "CONFIRM_REGENERATE_SSH_KEY" } - | { type: "CANCEL_REGENERATE_SSH_KEY" } | { type: "GET_AUTH_METHODS" } export const authMachine = @@ -128,12 +119,6 @@ export const authMachine = checkPermissions: { data: TypesGen.AuthorizationResponse } - getSSHKey: { - data: TypesGen.GitSSHKey - } - regenerateSSHKey: { - data: TypesGen.GitSSHKey - } hasFirstUser: { data: boolean } @@ -279,79 +264,6 @@ export const authMachine = }, }, }, - ssh: { - initial: "idle", - states: { - idle: { - on: { - GET_SSH_KEY: { - target: "gettingSSHKey", - }, - }, - }, - gettingSSHKey: { - entry: "clearGetSSHKeyError", - invoke: { - src: "getSSHKey", - onDone: [ - { - actions: "assignSSHKey", - target: "loaded", - }, - ], - onError: [ - { - actions: "assignGetSSHKeyError", - target: "idle", - }, - ], - }, - }, - loaded: { - initial: "idle", - states: { - idle: { - on: { - REGENERATE_SSH_KEY: { - target: "confirmSSHKeyRegenerate", - }, - }, - }, - confirmSSHKeyRegenerate: { - on: { - CANCEL_REGENERATE_SSH_KEY: { - target: "idle", - }, - CONFIRM_REGENERATE_SSH_KEY: { - target: "regeneratingSSHKey", - }, - }, - }, - regeneratingSSHKey: { - entry: "clearRegenerateSSHKeyError", - invoke: { - src: "regenerateSSHKey", - onDone: [ - { - actions: [ - "assignSSHKey", - "notifySuccessSSHKeyRegenerated", - ], - target: "idle", - }, - ], - onError: [ - { - actions: "assignRegenerateSSHKeyError", - target: "idle", - }, - ], - }, - }, - }, - }, - }, - }, methods: { initial: "idle", states: { @@ -480,9 +392,6 @@ export const authMachine = checks: permissionsToCheck, }) }, - // SSH - getSSHKey: () => API.getUserSSHKey(), - regenerateSSHKey: () => API.regenerateUserSSHKey(), // First user hasFirstUser: () => API.hasFirstUser(), }, @@ -538,25 +447,6 @@ export const authMachine = clearGetPermissionsError: assign({ checkPermissionsError: (_) => undefined, }), - // SSH - assignSSHKey: assign({ - sshKey: (_, event) => event.data, - }), - assignGetSSHKeyError: assign({ - getSSHKeyError: (_, event) => event.data, - }), - clearGetSSHKeyError: assign({ - getSSHKeyError: (_) => undefined, - }), - assignRegenerateSSHKeyError: assign({ - regenerateSSHKeyError: (_, event) => event.data, - }), - clearRegenerateSSHKeyError: assign({ - regenerateSSHKeyError: (_) => undefined, - }), - notifySuccessSSHKeyRegenerated: () => { - displaySuccess(Language.successRegenerateSSHKey) - }, redirect: (_, { data }) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- data can be undefined if (!data) { diff --git a/site/src/xServices/sshKey/sshKeyXService.ts b/site/src/xServices/sshKey/sshKeyXService.ts new file mode 100644 index 0000000000000..135a8a40393c8 --- /dev/null +++ b/site/src/xServices/sshKey/sshKeyXService.ts @@ -0,0 +1,125 @@ +import { getUserSSHKey, regenerateUserSSHKey } from "api/api" +import { GitSSHKey } from "api/typesGenerated" +import { displaySuccess } from "components/GlobalSnackbar/utils" +import { createMachine, assign } from "xstate" +import { i18n } from "i18n" + +const { t } = i18n + +interface Context { + sshKey?: GitSSHKey + getSSHKeyError?: unknown + regenerateSSHKeyError?: unknown +} + +type Events = + | { type: "REGENERATE_SSH_KEY" } + | { type: "CONFIRM_REGENERATE_SSH_KEY" } + | { type: "CANCEL_REGENERATE_SSH_KEY" } + +export const sshKeyMachine = createMachine( + { + id: "sshKeyState", + predictableActionArguments: true, + schema: { + context: {} as Context, + events: {} as Events, + services: {} as { + getSSHKey: { + data: GitSSHKey + } + regenerateSSHKey: { + data: GitSSHKey + } + }, + }, + tsTypes: {} as import("./sshKeyXService.typegen").Typegen0, + initial: "gettingSSHKey", + states: { + gettingSSHKey: { + entry: "clearGetSSHKeyError", + invoke: { + src: "getSSHKey", + onDone: [ + { + actions: "assignSSHKey", + target: "loaded", + }, + ], + onError: [ + { + actions: "assignGetSSHKeyError", + target: "notLoaded", + }, + ], + }, + }, + notLoaded: { + type: "final", + }, + loaded: { + on: { + REGENERATE_SSH_KEY: { + target: "confirmSSHKeyRegenerate", + }, + }, + }, + confirmSSHKeyRegenerate: { + on: { + CANCEL_REGENERATE_SSH_KEY: { + target: "loaded", + }, + CONFIRM_REGENERATE_SSH_KEY: { + target: "regeneratingSSHKey", + }, + }, + }, + regeneratingSSHKey: { + entry: "clearRegenerateSSHKeyError", + invoke: { + src: "regenerateSSHKey", + onDone: [ + { + actions: ["assignSSHKey", "notifySuccessSSHKeyRegenerated"], + target: "loaded", + }, + ], + onError: [ + { + actions: "assignRegenerateSSHKeyError", + target: "loaded", + }, + ], + }, + }, + }, + }, + { + services: { + getSSHKey: () => getUserSSHKey(), + regenerateSSHKey: () => regenerateUserSSHKey(), + }, + actions: { + assignSSHKey: assign({ + sshKey: (_, { data }) => data, + }), + assignGetSSHKeyError: assign({ + getSSHKeyError: (_, { data }) => data, + }), + clearGetSSHKeyError: assign({ + getSSHKeyError: (_) => undefined, + }), + assignRegenerateSSHKeyError: assign({ + regenerateSSHKeyError: (_, { data }) => data, + }), + clearRegenerateSSHKeyError: assign({ + regenerateSSHKeyError: (_) => undefined, + }), + notifySuccessSSHKeyRegenerated: () => { + displaySuccess( + t("sshRegenerateSuccessMessage", { ns: "userSettingsPage" }), + ) + }, + }, + }, +)