Forráskód Böngészése

fix(clients): use new email after rename and de-duplicate save toast

On client edit the post-update calls (attach/detach/externalLinks) keyed by the original email, so renaming a client made setExternalLinks fail with record-not-found. Key them by the updated email instead.

Each of those sub-step POSTs also auto-toasted its own success, so a save fired the 'Inbound client has been updated' toast twice (or more). Add a silentSuccess HttpUtil option that suppresses the redundant success toast while still surfacing errors and the node-offline warning, and apply it to the attach/detach/externalLinks mutations.
MHSanaei 5 órája
szülő
commit
23e73cd4a3

+ 3 - 3
frontend/src/hooks/useClients.ts

@@ -350,13 +350,13 @@ export function useClients() {
 
   const attachMut = useMutation({
     mutationFn: ({ email, inboundIds }: { email: string; inboundIds: number[] }) =>
-      HttpUtil.post(`/panel/api/clients/${encodeURIComponent(email)}/attach`, { inboundIds }, JSON_HEADERS),
+      HttpUtil.post(`/panel/api/clients/${encodeURIComponent(email)}/attach`, { inboundIds }, { ...JSON_HEADERS, silentSuccess: true }),
     onSuccess: (msg) => { if (msg?.success) invalidateAll(); },
   });
 
   const setExternalLinksMut = useMutation({
     mutationFn: ({ email, externalLinks }: { email: string; externalLinks: ExternalLinkInput[] }) =>
-      HttpUtil.post(`/panel/api/clients/${encodeURIComponent(email)}/externalLinks`, { externalLinks }, JSON_HEADERS),
+      HttpUtil.post(`/panel/api/clients/${encodeURIComponent(email)}/externalLinks`, { externalLinks }, { ...JSON_HEADERS, silentSuccess: true }),
     onSuccess: (msg) => { if (msg?.success) invalidateAll(); },
   });
 
@@ -370,7 +370,7 @@ export function useClients() {
 
   const detachMut = useMutation({
     mutationFn: ({ email, inboundIds }: { email: string; inboundIds: number[] }) =>
-      HttpUtil.post(`/panel/api/clients/${encodeURIComponent(email)}/detach`, { inboundIds }, JSON_HEADERS),
+      HttpUtil.post(`/panel/api/clients/${encodeURIComponent(email)}/detach`, { inboundIds }, { ...JSON_HEADERS, silentSuccess: true }),
     onSuccess: (msg) => { if (msg?.success) invalidateAll(); },
   });
 

+ 5 - 3
frontend/src/pages/clients/ClientsPage.tsx

@@ -685,16 +685,18 @@ export default function ClientsPage() {
     }
     const updateMsg = await update(meta.email, payload);
     if (!updateMsg?.success) return updateMsg;
+    const rawEmail = (payload as { email?: unknown }).email;
+    const emailKey = typeof rawEmail === 'string' && rawEmail.trim() ? rawEmail.trim() : meta.email;
     if (Array.isArray(meta.attach) && meta.attach.length > 0) {
-      const r = await attach(meta.email, meta.attach);
+      const r = await attach(emailKey, meta.attach);
       if (!r?.success) return r;
     }
     if (Array.isArray(meta.detach) && meta.detach.length > 0) {
-      const r = await detach(meta.email, meta.detach);
+      const r = await detach(emailKey, meta.detach);
       if (!r?.success) return r;
     }
     // Always replace the client's external links (an empty set clears them).
-    const r = await setExternalLinks(meta.email, meta.externalLinks);
+    const r = await setExternalLinks(emailKey, meta.externalLinks);
     if (!r?.success) return r;
     return updateMsg;
   }, [create, update, attach, detach, setExternalLinks]);

+ 19 - 14
frontend/src/utils/index.ts

@@ -19,6 +19,7 @@ export class Msg<T = unknown> {
 
 export interface HttpOptions extends AxiosRequestConfig {
   silent?: boolean;
+  silentSuccess?: boolean;
 }
 
 export interface HttpModal {
@@ -27,20 +28,24 @@ export interface HttpModal {
 }
 
 export class HttpUtil {
-  static _handleMsg(msg: unknown): void {
+  static _handleMsg(msg: unknown, silentSuccess = false): void {
     if (!(msg instanceof Msg) || msg.msg === '') {
       return;
     }
-    const messageType = msg.success ? 'success' : 'error';
-    getMessage()[messageType](msg.msg);
-    if (
-      msg.success &&
-      msg.obj &&
-      typeof msg.obj === 'object' &&
-      (msg.obj as { nodePending?: unknown }).nodePending === true
-    ) {
-      getMessage().warning(i18next.t('pages.inbounds.toasts.savedNodeOfflineWillSync'));
+    if (msg.success) {
+      if (!silentSuccess) {
+        getMessage().success(msg.msg);
+      }
+      if (
+        msg.obj &&
+        typeof msg.obj === 'object' &&
+        (msg.obj as { nodePending?: unknown }).nodePending === true
+      ) {
+        getMessage().warning(i18next.t('pages.inbounds.toasts.savedNodeOfflineWillSync'));
+      }
+      return;
     }
+    getMessage().error(msg.msg);
   }
 
   static _respToMsg(resp: AxiosResponse | undefined): Msg {
@@ -59,11 +64,11 @@ export class HttpUtil {
   }
 
   static async get<T = unknown>(url: string, params?: unknown, options: HttpOptions = {}): Promise<Msg<T>> {
-    const { silent, ...axiosOpts } = options;
+    const { silent, silentSuccess, ...axiosOpts } = options;
     try {
       const resp = await axios.get(url, { params, ...axiosOpts });
       const msg = this._respToMsg(resp) as Msg<T>;
-      if (!silent) this._handleMsg(msg);
+      if (!silent) this._handleMsg(msg, silentSuccess);
       return msg;
     } catch (error) {
       console.error('GET request failed:', error);
@@ -75,11 +80,11 @@ export class HttpUtil {
   }
 
   static async post<T = unknown>(url: string, data?: unknown, options: HttpOptions = {}): Promise<Msg<T>> {
-    const { silent, ...axiosOpts } = options;
+    const { silent, silentSuccess, ...axiosOpts } = options;
     try {
       const resp = await axios.post(url, data, axiosOpts);
       const msg = this._respToMsg(resp) as Msg<T>;
-      if (!silent) this._handleMsg(msg);
+      if (!silent) this._handleMsg(msg, silentSuccess);
       return msg;
     } catch (error) {
       console.error('POST request failed:', error);