This is an automated email from the ASF dual-hosted git repository.

juzhiyuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git


The following commit(s) were added to refs/heads/master by this push:
     new f8e2ffa64 feat(stores/global): use jotai (#3134)
f8e2ffa64 is described below

commit f8e2ffa6421a50e153c4fc95ee7d93d730a3ecdb
Author: YYYoung <isk...@outlook.com>
AuthorDate: Sat Jul 26 14:13:21 2025 +0800

    feat(stores/global): use jotai (#3134)
---
 package.json                              |  1 +
 pnpm-lock.yaml                            | 20 ++++++++++++++++
 src/components/Header/SettingModalBtn.tsx |  7 ++++--
 src/components/page/SettingsModal.tsx     | 14 ++++++++----
 src/config/req.ts                         |  8 ++++---
 src/stores/global.ts                      | 38 +++++++++++--------------------
 6 files changed, 53 insertions(+), 35 deletions(-)

diff --git a/package.json b/package.json
index ec87fd28e..54695874e 100644
--- a/package.json
+++ b/package.json
@@ -31,6 +31,7 @@
     "fast-clean": "^1.4.0",
     "i18next": "^25.0.1",
     "immer": "^10.1.1",
+    "jotai": "^2.12.5",
     "mobx": "^6.13.7",
     "mobx-persist-store": "^1.1.8",
     "mobx-react-lite": "^4.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9516c2516..8811eb958 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -62,6 +62,9 @@ importers:
       immer:
         specifier: ^10.1.1
         version: 10.1.1
+      jotai:
+        specifier: ^2.12.5
+        version: 2.12.5(@types/react@19.1.3)(react@19.1.0)
       mobx:
         specifier: ^6.13.7
         version: 6.13.7
@@ -2467,6 +2470,18 @@ packages:
     resolution: {integrity: 
sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
     hasBin: true
 
+  jotai@2.12.5:
+    resolution: {integrity: 
sha512-G8m32HW3lSmcz/4mbqx0hgJIQ0ekndKWiYP7kWVKi0p6saLXdSoye+FZiOFyonnd7Q482LCzm8sMDl7Ar1NWDw==}
+    engines: {node: '>=12.20.0'}
+    peerDependencies:
+      '@types/react': '>=17.0.0'
+      react: '>=17.0.0'
+    peerDependenciesMeta:
+      '@types/react':
+        optional: true
+      react:
+        optional: true
+
   js-cookie@2.2.1:
     resolution: {integrity: 
sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==}
 
@@ -6467,6 +6482,11 @@ snapshots:
 
   jiti@2.4.2: {}
 
+  jotai@2.12.5(@types/react@19.1.3)(react@19.1.0):
+    optionalDependencies:
+      '@types/react': 19.1.3
+      react: 19.1.0
+
   js-cookie@2.2.1: {}
 
   js-tokens@4.0.0: {}
diff --git a/src/components/Header/SettingModalBtn.tsx 
b/src/components/Header/SettingModalBtn.tsx
index a13ce1b15..1003d60fc 100644
--- a/src/components/Header/SettingModalBtn.tsx
+++ b/src/components/Header/SettingModalBtn.tsx
@@ -15,14 +15,17 @@
  * limitations under the License.
  */
 import { ActionIcon } from '@mantine/core';
+import { useSetAtom } from 'jotai';
 
-import { globalStore } from '@/stores/global';
+import { isSettingsOpenAtom } from '@/stores/global';
 import IconSettings from '~icons/material-symbols/settings';
 
 export const SettingModalBtn = () => {
+  const setIsSettingsOpen = useSetAtom(isSettingsOpenAtom);
+
   return (
     <ActionIcon
-      onClick={() => globalStore.settings.set('isOpen', true)}
+      onClick={() => setIsSettingsOpen(true)}
       variant="light"
       size="sm"
     >
diff --git a/src/components/page/SettingsModal.tsx 
b/src/components/page/SettingsModal.tsx
index 5cf8e8215..a9e582ee2 100644
--- a/src/components/page/SettingsModal.tsx
+++ b/src/components/page/SettingsModal.tsx
@@ -15,20 +15,23 @@
  * limitations under the License.
  */
 import { Divider, InputWrapper, Modal, Text, TextInput } from '@mantine/core';
+import { useAtom } from 'jotai';
 import { useTranslation } from 'react-i18next';
 
 import { queryClient } from '@/config/global';
-import { globalStore } from '@/stores/global';
+import { adminKeyAtom, isSettingsOpenAtom } from '@/stores/global';
 import { sha } from '~build/git';
 
 const AdminKey = () => {
   const { t } = useTranslation();
+  const [adminKey, setAdminKey] = useAtom(adminKeyAtom);
+
   return (
     <TextInput
       label={t('settings.adminKey')}
-      value={globalStore.settings.adminKey}
+      value={adminKey}
       onChange={(e) => {
-        globalStore.settings.set('adminKey', e.currentTarget.value);
+        setAdminKey(e.currentTarget.value);
         setTimeout(() => {
           queryClient.invalidateQueries();
           queryClient.refetchQueries();
@@ -52,11 +55,12 @@ const UICommitSha = () => {
 
 export const SettingsModal = () => {
   const { t } = useTranslation();
+  const [isSettingsOpen, setIsSettingsOpen] = useAtom(isSettingsOpenAtom);
 
   return (
     <Modal
-      opened={globalStore.settings.isOpen}
-      onClose={() => globalStore.settings.set('isOpen', false)}
+      opened={isSettingsOpen}
+      onClose={() => setIsSettingsOpen(false)}
       centered
       title={t('settings.title')}
     >
diff --git a/src/config/req.ts b/src/config/req.ts
index d0a6751ae..2f136c9c1 100644
--- a/src/config/req.ts
+++ b/src/config/req.ts
@@ -17,6 +17,7 @@
 
 import { notifications } from '@mantine/notifications';
 import axios, { AxiosError, type AxiosResponse, HttpStatusCode } from 'axios';
+import { getDefaultStore } from 'jotai';
 import { stringify } from 'qs';
 
 import {
@@ -24,7 +25,7 @@ import {
   API_PREFIX,
   SKIP_INTERCEPTOR_HEADER,
 } from '@/config/constant';
-import { globalStore } from '@/stores/global';
+import { adminKeyAtom, isSettingsOpenAtom } from '@/stores/global';
 
 export const req = axios.create();
 
@@ -40,7 +41,8 @@ req.interceptors.request.use((conf) => {
     });
   };
   conf.baseURL = API_PREFIX;
-  conf.headers.set(API_HEADER_KEY, globalStore.settings.adminKey);
+  const adminKey = getDefaultStore().get(adminKeyAtom);
+  conf.headers.set(API_HEADER_KEY, adminKey);
   return conf;
 });
 
@@ -84,7 +86,7 @@ req.interceptors.response.use(
       });
       // Requires to enter admin key at 401
       if (res.status === HttpStatusCode.Unauthorized) {
-        globalStore.settings.set('isOpen', true);
+        getDefaultStore().set(isSettingsOpenAtom, true);
         return Promise.resolve({ data: {} });
       }
     }
diff --git a/src/stores/global.ts b/src/stores/global.ts
index ed49aa37d..0b0e5a450 100644
--- a/src/stores/global.ts
+++ b/src/stores/global.ts
@@ -14,30 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { action, observable } from 'mobx';
-import { makePersistable } from 'mobx-persist-store';
+import { atom } from 'jotai';
+import { atomWithStorage } from 'jotai/utils';
 
-/** allow store use `set(key, value)` */
-const set = action(function <T, K extends keyof T>(
-  this: T,
-  key: K extends 'set' ? never : K,
-  value: T[K]
-) {
-  this[key as K] = value;
-});
+// Admin key with persistent storage
+export const adminKeyAtom = atomWithStorage<string>(
+  'settings:adminKey',
+  '',
+  undefined,
+  {
+    getOnInit: true,
+  }
+);
 
-const settingsStore = observable({
-  set,
-  isOpen: false,
-  adminKey: '',
-});
-
-export const globalStore = observable({
-  settings: settingsStore,
-});
-
-makePersistable(settingsStore, {
-  name: 'settings',
-  properties: ['adminKey'],
-  storage: window.localStorage,
-});
+// Settings modal visibility state
+export const isSettingsOpenAtom = atom<boolean>(false);

Reply via email to