Quellcode durchsuchen

chore: bump next-safe-action

Nicolas Meienberger vor 1 Jahr
Ursprung
Commit
fbefc31bd3

+ 2 - 4
package.json

@@ -62,7 +62,7 @@
     "lodash.merge": "^4.6.2",
     "next": "13.5.3",
     "next-intl": "^2.20.0",
-    "next-safe-action": "^3.0.1",
+    "next-safe-action": "^3.4.0",
     "pg": "^8.11.1",
     "qrcode.react": "^3.1.0",
     "react": "18.2.0",
@@ -158,8 +158,6 @@
   },
   "homepage": "https://github.com/meienberger/runtipi#readme",
   "pnpm": {
-    "patchedDependencies": {
-      "next-safe-action@3.0.1": "patches/next-safe-action@3.0.1.patch"
-    }
+    "patchedDependencies": {}
   }
 }

+ 0 - 312
patches/next-safe-action@3.0.1.patch

@@ -1,312 +0,0 @@
-diff --git a/dist/hook.d.ts b/dist/hook.d.ts
-index 42220517df6bad298dd77f2e1961d6798ecfef0d..7b32f3634610ae20c0b108034a7da8048bc96205 100644
---- a/dist/hook.d.ts
-+++ b/dist/hook.d.ts
-@@ -1,5 +1,9 @@
--import { z } from 'zod';
--import { C as ClientCaller, H as HookCallbacks, a as HookRes } from './types-31a698ec.js';
-+import { z } from "zod";
-+import {
-+  C as ClientCaller,
-+  H as HookCallbacks,
-+  a as HookRes,
-+} from "./types-31a698ec.js";
- 
- /**
-  * Use the action from a Client Component via hook.
-@@ -8,14 +12,17 @@ import { C as ClientCaller, H as HookCallbacks, a as HookRes } from './types-31a
-  *
-  * {@link https://github.com/TheEdoRan/next-safe-action/tree/main/packages/next-safe-action#2-the-hook-way See an example}
-  */
--declare const useAction: <const IV extends z.ZodTypeAny, const Data>(clientCaller: ClientCaller<IV, Data>, cb?: HookCallbacks<IV, Data> | undefined) => {
--    execute: (input: z.input<IV>) => void;
--    isExecuting: boolean;
--    res: HookRes<IV, Data>;
--    reset: () => void;
--    hasExecuted: boolean;
--    hasSucceded: boolean;
--    hasErrored: boolean;
-+declare const useAction: <const IV extends z.ZodTypeAny, const Data>(
-+  clientCaller: ClientCaller<IV, Data>,
-+  cb?: HookCallbacks<IV, Data> | undefined
-+) => {
-+  execute: (input: z.input<IV>) => void;
-+  isExecuting: boolean;
-+  res: HookRes<IV, Data>;
-+  reset: () => void;
-+  hasExecuted: boolean;
-+  hasSucceded: boolean;
-+  hasErrored: boolean;
- };
- /**
-  * Use the action from a Client Component via hook, with optimistic data update.
-@@ -27,15 +34,22 @@ declare const useAction: <const IV extends z.ZodTypeAny, const Data>(clientCalle
-  *
-  * {@link https://github.com/TheEdoRan/next-safe-action/tree/main/packages/next-safe-action#optimistic-update--experimental See an example}
-  */
--declare const useOptimisticAction: <const IV extends z.ZodTypeAny, const Data>(clientCaller: ClientCaller<IV, Data>, initialOptData: Data, cb?: HookCallbacks<IV, Data> | undefined) => {
--    execute: (input: z.input<IV>, newOptimisticData: Partial<Data>) => Promise<void>;
--    isExecuting: boolean;
--    res: HookRes<IV, Data>;
--    optimisticData: Data;
--    reset: () => void;
--    hasExecuted: boolean;
--    hasSucceded: boolean;
--    hasErrored: boolean;
-+declare const useOptimisticAction: <const IV extends z.ZodTypeAny, const Data>(
-+  clientCaller: ClientCaller<IV, Data>,
-+  initialOptData: Data,
-+  cb?: HookCallbacks<IV, Data> | undefined
-+) => {
-+  execute: (
-+    input: z.input<IV>,
-+    newOptimisticData: Partial<Data>
-+  ) => Promise<void>;
-+  isExecuting: boolean;
-+  res: HookRes<IV, Data>;
-+  optimisticData: Data;
-+  reset: () => void;
-+  hasExecuted: boolean;
-+  hasSucceded: boolean;
-+  hasErrored: boolean;
- };
- 
- export { HookCallbacks, HookRes, useAction, useOptimisticAction };
-diff --git a/dist/hook.mjs b/dist/hook.mjs
-index 89d77b1da720c87508e525f09e2c0005a1986819..5b0d50450441734813511cce9c6df203104c6f66 100644
---- a/dist/hook.mjs
-+++ b/dist/hook.mjs
-@@ -1,3 +1,5 @@
-+"use client";
-+
- // src/hook.ts
- import {
-   experimental_useOptimistic,
-@@ -5,31 +7,34 @@ import {
-   useEffect,
-   useRef,
-   useState,
--  useTransition
-+  useTransition,
- } from "react";
- 
- // src/utils.ts
--var isNextRedirectError = (e) => e instanceof Error && e.message === "NEXT_REDIRECT";
--var isNextNotFoundError = (e) => e instanceof Error && e.message === "NEXT_NOT_FOUND";
-+var isError = (e) => e instanceof Error;
-+var isNextRedirectError = (e) => isError(e) && e.message === "NEXT_REDIRECT";
-+var isNextNotFoundError = (e) => isError(e) && e.message === "NEXT_NOT_FOUND";
- 
- // src/hook.ts
--"use client";
- var getActionStatus = (res) => {
-   const hasSucceded = typeof res.data !== "undefined";
--  const hasErrored = typeof res.validationError !== "undefined" || typeof res.serverError !== "undefined" || typeof res.fetchError !== "undefined";
-+  const hasErrored =
-+    typeof res.validationError !== "undefined" ||
-+    typeof res.serverError !== "undefined" ||
-+    typeof res.fetchError !== "undefined";
-   const hasExecuted = hasSucceded || hasErrored;
-   return { hasExecuted, hasSucceded, hasErrored };
- };
--var useActionCallbacks = (res, hasSucceded, hasErrored, reset, cb) => {
-+var useActionCallbacks = (input, res, hasSucceded, hasErrored, reset, cb) => {
-   const onSuccessRef = useRef(cb?.onSuccess);
-   const onErrorRef = useRef(cb?.onError);
-   useEffect(() => {
-     const onSuccess = onSuccessRef.current;
-     const onError = onErrorRef.current;
-     if (onSuccess && hasSucceded) {
--      onSuccess(res.data, reset);
-+      onSuccess(res.data, reset, input);
-     } else if (onError && hasErrored) {
--      onError(res, reset);
-+      onError(res, reset, input);
-     }
-   }, [hasErrored, hasSucceded, res, reset]);
- };
-@@ -37,21 +42,31 @@ var useAction = (clientCaller, cb) => {
-   const [isExecuting, startTransition] = useTransition();
-   const executor = useRef(clientCaller);
-   const [res, setRes] = useState({});
-+  const [input, setInput] = useState();
-+  const onExecuteRef = useRef(cb?.onExecute);
-   const { hasExecuted, hasSucceded, hasErrored } = getActionStatus(res);
--  const execute = useCallback((input) => {
-+  const execute = useCallback((input2) => {
-+    setInput(input2);
-+    const onExecute = onExecuteRef.current;
-+    if (onExecute) {
-+      onExecute(input2);
-+    }
-     return startTransition(() => {
--      return executor.current(input).then((res2) => setRes(res2)).catch((e) => {
--        if (isNextRedirectError(e) || isNextNotFoundError(e)) {
--          throw e;
--        }
--        setRes({ fetchError: e });
--      });
-+      return executor
-+        .current(input2)
-+        .then((res2) => setRes(res2))
-+        .catch((e) => {
-+          if (isNextRedirectError(e) || isNextNotFoundError(e)) {
-+            throw e;
-+          }
-+          setRes({ fetchError: e });
-+        });
-     });
-   }, []);
-   const reset = useCallback(() => {
-     setRes({});
-   }, []);
--  useActionCallbacks(res, hasSucceded, hasErrored, reset, cb);
-+  useActionCallbacks(input, res, hasSucceded, hasErrored, reset, cb);
-   return {
-     execute,
-     isExecuting,
-@@ -59,34 +74,47 @@ var useAction = (clientCaller, cb) => {
-     reset,
-     hasExecuted,
-     hasSucceded,
--    hasErrored
-+    hasErrored,
-   };
- };
- var useOptimisticAction = (clientCaller, initialOptData, cb) => {
-   const [res, setRes] = useState({});
--  const [optState, syncState] = experimental_useOptimistic({ ...initialOptData, ...res.data, __isExecuting__: false }, (state, newState) => ({
--    ...state,
--    ...newState,
--    __isExecuting__: true
--  }));
-+  const [input, setInput] = useState();
-+  const [optState, syncState] = experimental_useOptimistic(
-+    { ...initialOptData, ...res.data, __isExecuting__: false },
-+    (state, newState) => ({
-+      ...state,
-+      ...newState,
-+      __isExecuting__: true,
-+    })
-+  );
-   const executor = useRef(clientCaller);
-+  const onExecuteRef = useRef(cb?.onExecute);
-   const { hasExecuted, hasSucceded, hasErrored } = getActionStatus(res);
-   const execute = useCallback(
--    (input, newOptimisticData) => {
-+    (input2, newOptimisticData) => {
-       syncState(newOptimisticData);
--      return executor.current(input).then((res2) => setRes(res2)).catch((e) => {
--        if (isNextRedirectError(e) || isNextNotFoundError(e)) {
--          throw e;
--        }
--        setRes({ fetchError: e });
--      });
-+      setInput(input2);
-+      const onExecute = onExecuteRef.current;
-+      if (onExecute) {
-+        onExecute(input2);
-+      }
-+      return executor
-+        .current(input2)
-+        .then((res2) => setRes(res2))
-+        .catch((e) => {
-+          if (isNextRedirectError(e) || isNextNotFoundError(e)) {
-+            throw e;
-+          }
-+          setRes({ fetchError: e });
-+        });
-     },
-     [syncState]
-   );
-   const reset = useCallback(() => {
-     setRes({});
-   }, []);
--  useActionCallbacks(res, hasSucceded, hasErrored, reset, cb);
-+  useActionCallbacks(input, res, hasSucceded, hasErrored, reset, cb);
-   const { __isExecuting__, ...optimisticData } = optState;
-   return {
-     execute,
-@@ -97,11 +125,8 @@ var useOptimisticAction = (clientCaller, initialOptData, cb) => {
-     reset,
-     hasExecuted,
-     hasSucceded,
--    hasErrored
-+    hasErrored,
-   };
- };
--export {
--  useAction,
--  useOptimisticAction
--};
-+export { useAction, useOptimisticAction };
- //# sourceMappingURL=hook.mjs.map
-diff --git a/dist/types-31a698ec.d.ts b/dist/types-31a698ec.d.ts
-index c9339a0a530dd1825af0c6e1b4a6e9a58adc3c97..1eb8b8303d1534deee9f0084b6c62c9eabb147b2 100644
---- a/dist/types-31a698ec.d.ts
-+++ b/dist/types-31a698ec.d.ts
-@@ -1,30 +1,53 @@
--import { z } from 'zod';
-+import { z } from "zod";
- 
- /**
-  * Type of the function called from Client Components with typesafe input data for the Server Action.
-  */
--type ClientCaller<IV extends z.ZodTypeAny, Data> = (input: z.input<IV>) => Promise<{
--    data?: Data;
--    serverError?: true;
--    validationError?: Partial<Record<keyof z.input<IV>, string[]>>;
-+type ClientCaller<IV extends z.ZodTypeAny, Data> = (
-+  input: z.input<IV>
-+) => Promise<{
-+  data?: Data;
-+  serverError?: string;
-+  validationError?: Partial<Record<keyof z.input<IV>, string[]>>;
- }>;
- /**
-  * Type of the function that executes server code when defining a new safe action.
-  */
--type ActionServerFn<IV extends z.ZodTypeAny, Data, Context extends object> = (parsedInput: z.input<IV>, ctx: Context) => Promise<Data>;
-+type ActionServerFn<IV extends z.ZodTypeAny, Data, Context extends object> = (
-+  parsedInput: z.input<IV>,
-+  ctx: Context
-+) => Promise<Data>;
- /**
-  * Type of `res` object returned by `useAction` and `useOptimisticAction` hooks.
-  */
--type HookRes<IV extends z.ZodTypeAny, Data> = Awaited<ReturnType<ClientCaller<IV, Data>>> & {
--    fetchError?: unknown;
-+type HookRes<IV extends z.ZodTypeAny, Data> = Awaited<
-+  ReturnType<ClientCaller<IV, Data>>
-+> & {
-+  fetchError?: unknown;
- };
- /**
-  * Type of hooks callbacks (`onSuccess` and `onError`).
-  * These are executed when the action succeeds or fails.
-  */
- type HookCallbacks<IV extends z.ZodTypeAny, Data> = {
--    onSuccess?: (data: NonNullable<Pick<HookRes<IV, Data>, "data">["data"]>, reset: () => void) => void;
--    onError?: (error: Omit<HookRes<IV, Data>, "data">, reset: () => void) => void;
-+  onSuccess?: (
-+    data: NonNullable<Pick<HookRes<IV, Data>, "data">["data"]>,
-+    reset: () => void,
-+    input: z.input<IV>
-+  ) => void;
-+  onError?: (
-+    error: Omit<HookRes<IV, Data>, "data">,
-+    reset: () => void,
-+    input: z.input<IV>
-+  ) => void;
-+  onExecute?: (input: z.input<IV>) => unknown;
- };
-+type MaybePromise<T> = T | Promise<T>;
- 
--export { ActionServerFn as A, ClientCaller as C, HookCallbacks as H, HookRes as a };
-+export {
-+  ActionServerFn as A,
-+  ClientCaller as C,
-+  HookCallbacks as H,
-+  MaybePromise as M,
-+  HookRes as a,
-+};

+ 11 - 16
pnpm-lock.yaml

@@ -1,10 +1,5 @@
 lockfileVersion: '6.0'
 
-patchedDependencies:
-  next-safe-action@3.0.1:
-    hash: i3wdronoj5npuplkzavpvyaj6e
-    path: patches/next-safe-action@3.0.1.patch
-
 importers:
 
   .:
@@ -100,8 +95,8 @@ importers:
         specifier: ^2.20.0
         version: 2.20.0(next@13.5.3)(react@18.2.0)
       next-safe-action:
-        specifier: ^3.0.1
-        version: 3.0.1(patch_hash=i3wdronoj5npuplkzavpvyaj6e)(next@13.5.3)(react@18.2.0)(zod@3.21.4)
+        specifier: ^3.4.0
+        version: 3.4.0(next@13.5.3)(react@18.2.0)(zod@3.21.4)
       pg:
         specifier: ^8.11.1
         version: 8.11.1
@@ -525,7 +520,7 @@ packages:
       '@babel/helper-validator-option': 7.22.5
       browserslist: 4.21.5
       lru-cache: 5.1.1
-      semver: 6.3.0
+      semver: 6.3.1
 
   /@babel/helper-environment-visitor@7.22.5:
     resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==}
@@ -2055,7 +2050,7 @@ packages:
       nopt: 5.0.0
       npmlog: 5.0.1
       rimraf: 3.0.2
-      semver: 7.5.3
+      semver: 7.5.4
       tar: 6.1.13
     transitivePeerDependencies:
       - encoding
@@ -3781,7 +3776,7 @@ packages:
       '@typescript-eslint/typescript-estree': 5.59.11(typescript@5.2.2)
       eslint: 8.50.0
       eslint-scope: 5.1.1
-      semver: 7.5.3
+      semver: 7.5.4
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -5086,6 +5081,7 @@ packages:
 
   /defaults@1.0.4:
     resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
+    requiresBuild: true
     dependencies:
       clone: 1.0.4
     dev: true
@@ -5644,7 +5640,7 @@ packages:
       eslint-plugin-import: 2.27.5(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-typescript@3.5.5)(eslint@8.50.0)
       object.assign: 4.1.4
       object.entries: 1.1.6
-      semver: 6.3.0
+      semver: 6.3.1
     dev: true
 
   /eslint-config-airbnb-typescript@17.0.0(@typescript-eslint/eslint-plugin@6.7.3)(@typescript-eslint/parser@6.7.3)(eslint-plugin-import@2.27.5)(eslint@8.50.0):
@@ -6049,7 +6045,7 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     hasBin: true
     dependencies:
-      '@eslint-community/eslint-utils': 4.3.0(eslint@8.50.0)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0)
       '@eslint-community/regexpp': 4.9.0
       '@eslint/eslintrc': 2.1.2
       '@eslint/js': 8.50.0
@@ -8918,8 +8914,8 @@ packages:
       react: 18.2.0
     dev: true
 
-  /next-safe-action@3.0.1(patch_hash=i3wdronoj5npuplkzavpvyaj6e)(next@13.5.3)(react@18.2.0)(zod@3.21.4):
-    resolution: {integrity: sha512-qQOHz4Z1vnW9fKAl3+nmSoONtX8kvqJBJJ4PkRlkSF8AfFJnYp7PZ5qvtdIBTzxNoQLtM/CyVqlAM/6dCHJ62w==}
+  /next-safe-action@3.4.0(next@13.5.3)(react@18.2.0)(zod@3.21.4):
+    resolution: {integrity: sha512-EUmcChSlfIjdHu2n6L4w3EQnb1Np9C2OEWObPSKggdK/IhihDDatsunZKGcuimxFO3JbBdpFiUYcMe2slbovUg==}
     engines: {node: '>=16'}
     peerDependencies:
       next: '>= 13.4.2'
@@ -8930,7 +8926,6 @@ packages:
       react: 18.2.0
       zod: 3.21.4
     dev: false
-    patched: true
 
   /next@13.5.3(@babel/core@7.22.5)(react-dom@18.2.0)(react@18.2.0)(sass@1.63.6):
     resolution: {integrity: sha512-4Nt4HRLYDW/yRpJ/QR2t1v63UOMS55A38dnWv3UDOWGezuY0ZyFO1ABNbD7mulVzs9qVhgy2+ppjdsANpKP1mg==}
@@ -10689,7 +10684,7 @@ packages:
       methods: 1.1.2
       mime: 2.6.0
       qs: 6.11.0
-      semver: 7.5.3
+      semver: 7.5.4
     transitivePeerDependencies:
       - supports-color
     dev: true

+ 0 - 46
src/app/(dashboard)/settings/components/ChangePasswordForm/ChangePasswordForm.test.tsx

@@ -1,55 +1,9 @@
 import React from 'react';
-import { server } from '@/client/mocks/server';
-import { getTRPCMock, getTRPCMockError } from '@/client/mocks/getTrpcMock';
 import { faker } from '@faker-js/faker';
 import { render, screen, waitFor, fireEvent } from '../../../../../../tests/test-utils';
 import { ChangePasswordForm } from './ChangePasswordForm';
 
 describe('<ChangePasswordForm />', () => {
-  it('should show success toast upon password change', async () => {
-    // arrange
-    server.use(getTRPCMock({ path: ['auth', 'changePassword'], type: 'mutation', response: true }));
-    render(<ChangePasswordForm />);
-    const currentPasswordInput = screen.getByRole('textbox', { name: 'currentPassword' });
-    const newPasswordInput = screen.getByRole('textbox', { name: 'newPassword' });
-    const confirmPasswordInput = screen.getByRole('textbox', { name: 'newPasswordConfirm' });
-    const newPassword = faker.string.alphanumeric(8);
-
-    // act
-    fireEvent.change(currentPasswordInput, { target: { value: 'test' } });
-    fireEvent.change(newPasswordInput, { target: { value: newPassword } });
-    fireEvent.change(confirmPasswordInput, { target: { value: newPassword } });
-    const submitButton = screen.getByRole('button', { name: /Change password/i });
-    submitButton.click();
-
-    // assert
-    await waitFor(() => {
-      expect(screen.getByText('Password changed successfully')).toBeInTheDocument();
-    });
-  });
-
-  it('should show error toast if change password failed', async () => {
-    // arrange
-    server.use(getTRPCMockError({ path: ['auth', 'changePassword'], type: 'mutation', message: 'Invalid password' }));
-    render(<ChangePasswordForm />);
-    const currentPasswordInput = screen.getByRole('textbox', { name: 'currentPassword' });
-    const newPasswordInput = screen.getByRole('textbox', { name: 'newPassword' });
-    const confirmPasswordInput = screen.getByRole('textbox', { name: 'newPasswordConfirm' });
-    const newPassword = faker.string.alphanumeric(8);
-
-    // act
-    fireEvent.change(currentPasswordInput, { target: { value: faker.string.alphanumeric(8) } });
-    fireEvent.change(newPasswordInput, { target: { value: newPassword } });
-    fireEvent.change(confirmPasswordInput, { target: { value: newPassword } });
-    const submitButton = screen.getByRole('button', { name: /Change password/i });
-    submitButton.click();
-
-    // assert
-    await waitFor(() => {
-      expect(screen.getByText(/Invalid password/)).toBeInTheDocument();
-    });
-  });
-
   it('should show error in the form if passwords do not match', async () => {
     // arrange
     render(<ChangePasswordForm />);

+ 0 - 285
src/app/(dashboard)/settings/components/OtpForm/OptForm.test.tsx

@@ -1,285 +0,0 @@
-import React from 'react';
-import { server } from '@/client/mocks/server';
-import { getTRPCMock, getTRPCMockError } from '@/client/mocks/getTrpcMock';
-import { render, screen, waitFor, fireEvent } from '../../../../../../tests/test-utils';
-import { OtpForm } from './OtpForm';
-
-describe('<OtpForm />', () => {
-  it('should render', () => {
-    render(<OtpForm />);
-  });
-
-  it('should prompt for password when enabling 2FA', async () => {
-    // arrange
-    render(<OtpForm />);
-    const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
-    await waitFor(() => {
-      expect(twoFactorAuthButton).toBeEnabled();
-    });
-
-    // act
-    twoFactorAuthButton.click();
-
-    // assert
-    await waitFor(() => {
-      expect(screen.getByText('Password needed')).toBeInTheDocument();
-    });
-  });
-
-  it('should prompt for password when disabling 2FA', async () => {
-    // arrange
-    server.use(getTRPCMock({ path: ['auth', 'me'], response: { totpEnabled: true, id: 12, username: 'test', locale: 'en', operator: true } }));
-    render(<OtpForm />);
-    const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
-    await waitFor(() => {
-      expect(twoFactorAuthButton).toBeEnabled();
-    });
-
-    // act
-    twoFactorAuthButton.click();
-
-    // assert
-    await waitFor(() => {
-      expect(screen.getByText('Password needed')).toBeInTheDocument();
-    });
-  });
-
-  it('should show show error toast if password is incorrect while enabling 2FA', async () => {
-    // arrange
-    server.use(getTRPCMock({ path: ['auth', 'me'], response: { totpEnabled: false, id: 12, username: 'test', locale: 'en', operator: true } }));
-    server.use(getTRPCMockError({ path: ['auth', 'getTotpUri'], type: 'mutation', message: 'Invalid password' }));
-    render(<OtpForm />);
-    const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
-    await waitFor(() => {
-      expect(twoFactorAuthButton).toBeEnabled();
-    });
-
-    // act
-    twoFactorAuthButton.click();
-
-    await waitFor(() => {
-      expect(screen.getByText('Password needed')).toBeInTheDocument();
-    });
-
-    const passwordInput = screen.getByRole('textbox', { name: 'password' });
-    fireEvent.change(passwordInput, { target: { value: 'test' } });
-    const submitButton = screen.getByRole('button', { name: /Enable two-factor authentication/i });
-    submitButton.click();
-
-    // assert
-    await waitFor(() => {
-      expect(screen.getByText(/Invalid password/)).toBeInTheDocument();
-    });
-  });
-
-  it('should show show error toast if password is incorrect while disabling 2FA', async () => {
-    // arrange
-    server.use(getTRPCMock({ path: ['auth', 'me'], response: { locale: 'en', totpEnabled: true, id: 12, username: 'test', operator: true } }));
-    server.use(getTRPCMockError({ path: ['auth', 'disableTotp'], type: 'mutation', message: 'Invalid password' }));
-    render(<OtpForm />);
-
-    const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
-    await waitFor(() => {
-      expect(twoFactorAuthButton).toBeEnabled();
-    });
-
-    // act
-    twoFactorAuthButton.click();
-
-    await waitFor(() => {
-      expect(screen.getByText('Password needed')).toBeInTheDocument();
-    });
-
-    const passwordInput = screen.getByRole('textbox', { name: 'password' });
-    fireEvent.change(passwordInput, { target: { value: 'test' } });
-    const submitButton = screen.getByRole('button', { name: /Disable two-factor authentication/i });
-    submitButton.click();
-
-    // assert
-    await waitFor(() => {
-      expect(screen.getByText(/Invalid password/)).toBeInTheDocument();
-    });
-  });
-
-  it('should show success toast if password is correct while disabling 2FA', async () => {
-    // arrange
-    server.use(getTRPCMock({ path: ['auth', 'me'], response: { locale: 'en', totpEnabled: true, id: 12, username: 'test', operator: true } }));
-    server.use(getTRPCMock({ path: ['auth', 'disableTotp'], type: 'mutation', response: true }));
-
-    render(<OtpForm />);
-
-    const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
-    await waitFor(() => {
-      expect(twoFactorAuthButton).toBeEnabled();
-    });
-
-    // act
-    twoFactorAuthButton.click();
-
-    await waitFor(() => {
-      expect(screen.getByText('Password needed')).toBeInTheDocument();
-    });
-
-    const passwordInput = screen.getByRole('textbox', { name: 'password' });
-    fireEvent.change(passwordInput, { target: { value: 'test' } });
-    const submitButton = screen.getByRole('button', { name: /Disable two-factor authentication/i });
-    submitButton.click();
-
-    // assert
-    await waitFor(() => {
-      expect(screen.getByText('Two-factor authentication disabled')).toBeInTheDocument();
-    });
-  });
-
-  it('should show secret key and QR code when enabling 2FA', async () => {
-    // arrange
-    server.use(getTRPCMock({ path: ['auth', 'getTotpUri'], type: 'mutation', response: { key: 'test', uri: 'test' } }));
-    render(<OtpForm />);
-    const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
-    await waitFor(() => {
-      expect(twoFactorAuthButton).toBeEnabled();
-    });
-
-    // act
-    twoFactorAuthButton.click();
-    await waitFor(() => {
-      expect(screen.getByText('Password needed')).toBeInTheDocument();
-    });
-    const passwordInput = screen.getByRole('textbox', { name: 'password' });
-    fireEvent.change(passwordInput, { target: { value: 'test' } });
-    const submitButton = screen.getByRole('button', { name: /Enable two-factor authentication/i });
-    submitButton.click();
-
-    // assert
-    await waitFor(() => {
-      expect(screen.getByText('Scan this QR code with your authenticator app.')).toBeInTheDocument();
-    });
-    expect(screen.getByRole('textbox', { name: 'secret key' })).toHaveValue('test');
-    expect(screen.getByRole('button', { name: 'Enable two-factor authentication' })).toBeDisabled();
-  });
-
-  it('should show error toast if submitted totp code is invalid', async () => {
-    // arrange
-    server.use(getTRPCMock({ path: ['auth', 'getTotpUri'], type: 'mutation', response: { key: 'test', uri: 'test' } }));
-    server.use(getTRPCMockError({ path: ['auth', 'setupTotp'], type: 'mutation', message: 'Invalid code' }));
-
-    render(<OtpForm />);
-
-    const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
-    await waitFor(() => {
-      expect(twoFactorAuthButton).toBeEnabled();
-    });
-
-    // act
-    twoFactorAuthButton.click();
-    await waitFor(() => {
-      expect(screen.getByText('Password needed')).toBeInTheDocument();
-    });
-    const passwordInput = screen.getByRole('textbox', { name: 'password' });
-    fireEvent.change(passwordInput, { target: { value: 'test' } });
-    const submitButton = screen.getByRole('button', { name: /Enable two-factor authentication/i });
-    submitButton.click();
-
-    await waitFor(() => {
-      expect(screen.getByText('Scan this QR code with your authenticator app.')).toBeInTheDocument();
-    });
-
-    const inputEls = screen.getAllByRole('textbox', { name: /digit-/ });
-
-    inputEls.forEach((inputEl) => {
-      fireEvent.change(inputEl, { target: { value: '1' } });
-    });
-
-    const enable2FAButton = screen.getByRole('button', { name: 'Enable two-factor authentication' });
-    enable2FAButton.click();
-
-    // assert
-    await waitFor(() => {
-      expect(screen.getByText(/Invalid code/)).toBeInTheDocument();
-    });
-  });
-
-  it('should show success toast if submitted totp code is valid', async () => {
-    // arrange
-    server.use(getTRPCMock({ path: ['auth', 'getTotpUri'], type: 'mutation', response: { key: 'test', uri: 'test' } }));
-    server.use(getTRPCMock({ path: ['auth', 'setupTotp'], type: 'mutation', response: true }));
-    render(<OtpForm />);
-    const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
-    await waitFor(() => {
-      expect(twoFactorAuthButton).toBeEnabled();
-    });
-
-    // act
-    twoFactorAuthButton.click();
-    await waitFor(() => {
-      expect(screen.getByText('Password needed')).toBeInTheDocument();
-    });
-    const passwordInput = screen.getByRole('textbox', { name: 'password' });
-    fireEvent.change(passwordInput, { target: { value: 'test' } });
-    const submitButton = screen.getByRole('button', { name: /Enable two-factor authentication/i });
-    submitButton.click();
-
-    await waitFor(() => {
-      expect(screen.getByText('Scan this QR code with your authenticator app.')).toBeInTheDocument();
-    });
-
-    const inputEls = screen.getAllByRole('textbox', { name: /digit-/ });
-
-    inputEls.forEach((inputEl) => {
-      fireEvent.change(inputEl, { target: { value: '1' } });
-    });
-
-    const enable2FAButton = screen.getByRole('button', { name: 'Enable two-factor authentication' });
-    enable2FAButton.click();
-
-    // assert
-    await waitFor(() => {
-      expect(screen.getByText('Two-factor authentication enabled')).toBeInTheDocument();
-    });
-  });
-
-  it('can close the setup modal by clicking on the esc key', async () => {
-    // arrange
-    render(<OtpForm />);
-    const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
-    await waitFor(() => {
-      expect(twoFactorAuthButton).toBeEnabled();
-    });
-
-    // act
-    twoFactorAuthButton.click();
-    await waitFor(() => {
-      expect(screen.getByText('Password needed')).toBeInTheDocument();
-    });
-
-    fireEvent.keyDown(document, { key: 'Escape' });
-
-    // assert
-    await waitFor(() => {
-      expect(screen.queryByText('Password needed')).not.toBeInTheDocument();
-    });
-  });
-
-  it('can close the disable modal by clicking on the esc key', async () => {
-    // arrange
-    server.use(getTRPCMock({ path: ['auth', 'me'], response: { locale: 'en', totpEnabled: true, username: '', id: 1, operator: true } }));
-    render(<OtpForm />);
-    const twoFactorAuthButton = screen.getByRole('switch', { name: /Enable two-factor authentication/i });
-    await waitFor(() => {
-      expect(twoFactorAuthButton).toBeEnabled();
-    });
-
-    // act
-    twoFactorAuthButton.click();
-    await waitFor(() => {
-      expect(screen.getByText('Password needed')).toBeInTheDocument();
-    });
-
-    fireEvent.keyDown(document, { key: 'Escape' });
-
-    // assert
-    await waitFor(() => {
-      expect(screen.queryByText('Password needed')).not.toBeInTheDocument();
-    });
-  });
-});

+ 0 - 2
src/app/(dashboard)/settings/components/OtpForm/OtpForm.tsx

@@ -50,7 +50,6 @@ export const OtpForm = (props: { totpEnabled: boolean }) => {
         setKey('');
         setUri('');
         toast.success(t('2fa-enable-success'));
-        // ctx.auth.me.invalidate();
       }
     },
   });
@@ -65,7 +64,6 @@ export const OtpForm = (props: { totpEnabled: boolean }) => {
         toast.error(data.failure.reason);
       } else {
         toast.success(t('2fa-disable-success'));
-        //ctx.auth.me.invalidate();
       }
     },
   });

+ 1 - 1
src/app/(dashboard)/settings/components/SecurityContainer/SecurityContainer.test.tsx

@@ -4,6 +4,6 @@ import { SecurityContainer } from './SecurityContainer';
 
 describe('<SecurityContainer />', () => {
   it('should render', () => {
-    render(<SecurityContainer />);
+    render(<SecurityContainer totpEnabled={false} />);
   });
 });

+ 2 - 2
src/app/(dashboard)/settings/page.tsx

@@ -5,11 +5,11 @@ import React from 'react';
 import { SystemServiceClass } from '@/server/services/system';
 import { getSettings } from '@/server/core/TipiConfig';
 import { getCurrentLocale } from 'src/utils/getCurrentLocale';
+import { getUserFromCookie } from '@/server/common/session.helpers';
 import { SettingsTabTriggers } from './components/SettingsTabTriggers';
 import { GeneralActions } from './components/GeneralActions';
 import { SettingsContainer } from './components/SettingsContainer';
 import { SecurityContainer } from './components/SecurityContainer';
-import { getUserFromCookie } from '@/server/common/session.helpers';
 
 export async function generateMetadata(): Promise<Metadata> {
   const translator = await getTranslatorFromCookie();
@@ -29,7 +29,7 @@ export default async function SettingsPage({ searchParams }: { searchParams: { t
 
   return (
     <div className="card d-flex">
-      <Tabs defaultValue={(tab as string) || 'actions'}>
+      <Tabs defaultValue={tab || 'actions'}>
         <SettingsTabTriggers />
         <TabsContent value="actions">
           <GeneralActions version={version} />