Repository: invoiceninja/ui
Pull request #2389: [Cleanup] "Send Time" Confirmation Modal | Email Settings
Author: login: Civolilah
Labels: name: QA passed
Created: 2025-03-01T18:52:09Z | Merged: 2025-03-05T10:48:35Z

@beganovich @turbo124 The PR includes the implementation of a "Send Time" confirmation modal if Send Time is changed, with a message to sync it with Recurring Invoices and Reminders. If the user confirms that they want to sync, I've added "?sync_send_time=true" to the "/api/v1/companies" query. Screenshot:

<img width="1202" alt="Screenshot 2025-03-01 at 19 28 03" src="https://github.com/user-attachments/assets/fe224a3e-cdc2-45f7-a809-872ecdcb50b5" />

`Note`: Modal contains plain message text, but I replaced it with the "sync_send_time" translation keyword. If you agree with it, please just add it in the translation files.

Let me know your thoughts.



Diff:
diff --git a/src/pages/clients/create/Create.tsx b/src/pages/clients/create/Create.tsx
index 5c0193ce0e..2e34f3e942 100644
--- a/src/pages/clients/create/Create.tsx
+++ b/src/pages/clients/create/Create.tsx
@@ -94,7 +94,7 @@ export default function Create() {
       return onSave;
     }
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     request('POST', endpoint('/api/v1/clients'), client)
       .then((response) => {
diff --git a/src/pages/clients/edit/Edit.tsx b/src/pages/clients/edit/Edit.tsx
index f90d1fa7ef..0470c45380 100644
--- a/src/pages/clients/edit/Edit.tsx
+++ b/src/pages/clients/edit/Edit.tsx
@@ -103,7 +103,7 @@ export default function Edit() {
   const onSave = async () => {
     toast.processing();
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     request('PUT', endpoint('/api/v1/clients/:id', { id }), {
       ...client,
diff --git a/src/pages/credits/common/hooks.tsx b/src/pages/credits/common/hooks.tsx
index 5611505496..210a1a18d9 100644
--- a/src/pages/credits/common/hooks.tsx
+++ b/src/pages/credits/common/hooks.tsx
@@ -234,7 +234,7 @@ export function useCreate(props: CreateProps) {
     toast.processing();
     setErrors(undefined);
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     let apiEndpoint = '/api/v1/credits?';
 
@@ -292,7 +292,7 @@ export function useSave(props: CreateProps) {
 
     setErrors(undefined);
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     let apiEndpoint = '/api/v1/credits/:id?';
 
diff --git a/src/pages/invoices/create/hooks/useHandleCreate.ts b/src/pages/invoices/create/hooks/useHandleCreate.ts
index e03540da7c..ab240d594c 100644
--- a/src/pages/invoices/create/hooks/useHandleCreate.ts
+++ b/src/pages/invoices/create/hooks/useHandleCreate.ts
@@ -43,7 +43,7 @@ export function useHandleCreate(params: Params) {
     toast.processing();
     setErrors(undefined);
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     let apiEndpoint = '/api/v1/invoices?';
 
diff --git a/src/pages/invoices/edit/hooks/useInvoiceSave.ts b/src/pages/invoices/edit/hooks/useInvoiceSave.ts
index 968be92793..0daa2c641f 100644
--- a/src/pages/invoices/edit/hooks/useInvoiceSave.ts
+++ b/src/pages/invoices/edit/hooks/useInvoiceSave.ts
@@ -39,7 +39,7 @@ export function useHandleSave(params: Params) {
     setErrors(undefined);
     toast.processing();
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     let apiEndpoint = '/api/v1/invoices/:id?';
 
diff --git a/src/pages/payments/edit/hooks/useSave.ts b/src/pages/payments/edit/hooks/useSave.ts
index 8716ec72ac..ee51e0710e 100644
--- a/src/pages/payments/edit/hooks/useSave.ts
+++ b/src/pages/payments/edit/hooks/useSave.ts
@@ -27,14 +27,14 @@ export function useSave(
 
     toast.processing();
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     const adjustedPaymentPayload = { ...payment };
 
     delete adjustedPaymentPayload.invoices;
     delete adjustedPaymentPayload.credits;
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     request(
       'PUT',
diff --git a/src/pages/products/Product.tsx b/src/pages/products/Product.tsx
index d1cb6db8ac..ac9117f4d3 100644
--- a/src/pages/products/Product.tsx
+++ b/src/pages/products/Product.tsx
@@ -71,7 +71,7 @@ export default function Product() {
 
       toast.processing();
 
-      await saveCompany(true);
+      await saveCompany({ excludeToasters: true });
 
       const url = searchParams.has('update_in_stock_quantity')
         ? endpoint('/api/v1/products/:id?update_in_stock_quantity=true', { id })
diff --git a/src/pages/quotes/common/hooks.tsx b/src/pages/quotes/common/hooks.tsx
index edd1eafc59..b814dba2bd 100644
--- a/src/pages/quotes/common/hooks.tsx
+++ b/src/pages/quotes/common/hooks.tsx
@@ -165,7 +165,7 @@ export function useQuoteUtilities(props: QuoteUtilitiesProps) {
     if (lineItems[index][key] === value) {
       return;
     }
-    
+
     // eslint-disable-next-line @typescript-eslint/ban-ts-comment
     // @ts-ignore
     lineItems[index][key] = value;
@@ -240,7 +240,7 @@ export function useCreate(props: CreateProps) {
     toast.processing();
     setErrors(undefined);
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     let apiEndpoint = '/api/v1/quotes?';
 
@@ -294,7 +294,7 @@ export function useSave(props: CreateProps) {
     toast.processing();
     setErrors(undefined);
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     let apiEndpoint = '/api/v1/quotes/:id?';
 
diff --git a/src/pages/settings/common/hooks/useHandleCompanySave.tsx b/src/pages/settings/common/hooks/useHandleCompanySave.tsx
index 9bd67aa13e..b39587efc6 100644
--- a/src/pages/settings/common/hooks/useHandleCompanySave.tsx
+++ b/src/pages/settings/common/hooks/useHandleCompanySave.tsx
@@ -15,7 +15,7 @@ import { useDispatch } from 'react-redux';
 import { request } from '$app/common/helpers/request';
 import { ValidationBag } from '$app/common/interfaces/validation-bag';
 import { toast } from '$app/common/helpers/toast/toast';
-import { useAtom } from 'jotai';
+import { useAtom, useSetAtom } from 'jotai';
 import { companySettingsErrorsAtom } from '../atoms';
 import { useInjectCompanyChanges } from '$app/common/hooks/useInjectCompanyChanges';
 import { hasLanguageChanged as hasLanguageChangedAtom } from '$app/pages/settings/localization/common/atoms';
@@ -25,12 +25,18 @@ import { useHandleUpdate } from '../../group-settings/common/hooks/useHandleUpda
 import { useUpdateClientSettings } from '$app/pages/clients/common/hooks/useUpdateClientSettings';
 import { $refetch } from '$app/common/hooks/useRefetch';
 
+interface SaveOptions {
+  excludeToasters?: boolean;
+  syncSendTime?: boolean;
+}
+
 export function useHandleCompanySave() {
   const dispatch = useDispatch();
+
   const companyChanges = useInjectCompanyChanges();
 
+  const shouldUpdate = useShouldUpdateCompany();
   const handleUpdateGroupSettings = useHandleUpdate({});
-
   const updateClientSettings = useUpdateClientSettings();
 
   const {
@@ -39,15 +45,15 @@ export function useHandleCompanySave() {
     isClientSettingsActive,
   } = useCurrentSettingsLevel();
 
-  const [, setErrors] = useAtom(companySettingsErrorsAtom);
+  const setErrors = useSetAtom(companySettingsErrorsAtom);
 
   const [hasLanguageChanged, setHasLanguageIdChanged] = useAtom(
     hasLanguageChangedAtom
   );
 
-  const shouldUpdate = useShouldUpdateCompany();
+  return async (options?: SaveOptions) => {
+    const { excludeToasters = false, syncSendTime } = options || {};
 
-  return async (excludeToasters?: boolean) => {
     if (!shouldUpdate() && isCompanySettingsActive) {
       return;
     }
@@ -67,9 +73,15 @@ export function useHandleCompanySave() {
 
     setErrors(undefined);
 
+    let endpointUrl = '/api/v1/companies/:id';
+
+    if (typeof syncSendTime === 'boolean') {
+      endpointUrl += '?sync_send_time=' + syncSendTime;
+    }
+
     return request(
       'PUT',
-      endpoint('/api/v1/companies/:id', { id: companyChanges?.id }),
+      endpoint(endpointUrl, { id: companyChanges?.id }),
       companyChanges
     )
       .then((response) => {
diff --git a/src/pages/settings/email-settings/EmailSettings.tsx b/src/pages/settings/email-settings/EmailSettings.tsx
index 5d05d68f3b..23f9d84a5e 100644
--- a/src/pages/settings/email-settings/EmailSettings.tsx
+++ b/src/pages/settings/email-settings/EmailSettings.tsx
@@ -36,6 +36,9 @@ import { SMTPMailDriver } from './common/components/SMTPMailDriver';
 import { proPlan } from '$app/common/guards/guards/pro-plan';
 import { enterprisePlan } from '$app/common/guards/guards/enterprise-plan';
 import reactStringReplace from 'react-string-replace';
+import { useState } from 'react';
+import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany';
+import { SendTimeModal } from './common/components/SendTimeModal';
 
 export function EmailSettings() {
   useTitle('email_settings');
@@ -47,488 +50,515 @@ export function EmailSettings() {
     { name: t('email_settings'), href: '/settings/email_settings' },
   ];
 
-  const emailProviders = useEmailProviders();
-
   const company = useInjectCompanyChanges();
-
-  const disableSettingsField = useDisableSettingsField();
+  const currentCompany = useCurrentCompany();
+  const emailProviders = useEmailProviders();
+  const showPlanAlert = useShouldDisableAdvanceSettings();
 
   const errors = useAtomValue(companySettingsErrorsAtom);
 
-  const handleChange = useHandleCurrentCompanyChangeProperty();
+  const [isSendTimeModalOpen, setIsSendTimeModalOpen] =
+    useState<boolean>(false);
 
-  const onSave = useHandleCompanySave();
   const onCancel = useDiscardChanges();
+  const onSave = useHandleCompanySave();
+  const disableSettingsField = useDisableSettingsField();
+  const handleChange = useHandleCurrentCompanyChangeProperty();
 
-  const showPlanAlert = useShouldDisableAdvanceSettings();
+  const handleOnSaveClick = () => {
+    if (
+      currentCompany?.settings.entity_send_time !==
+      company?.settings.entity_send_time
+    ) {
+      setIsSendTimeModalOpen(true);
+    } else {
+      onSave();
+    }
+  };
 
   return (
-    <Settings
-      title={t('email_settings')}
-      docsLink="en/advanced-settings/#email_settings"
-      breadcrumbs={pages}
-      onSaveClick={onSave}
-      onCancelClick={onCancel}
-      disableSaveButton={showPlanAlert}
-    >
-      <AdvancedSettingsPlanAlert />
-
-      <Card title={t('settings')}>
-        <Element
-          leftSide={
-            <PropertyCheckbox
-              propertyKey="show_email_footer"
-              labelElement={<SettingsLabel label={t('show_email_footer')} />}
-            />
-          }
-        >
-          <Toggle
-            checked={Boolean(company?.settings.show_email_footer)}
-            onValueChange={(value) =>
-              handleChange('settings.show_email_footer', value)
-            }
-            disabled={disableSettingsField('show_email_footer')}
-          />
-        </Element>
-
-        <Element
-          leftSide={
-            <PropertyCheckbox
-              propertyKey="pdf_email_attachment"
-              labelElement={<SettingsLabel label={t('attach_pdf')} />}
-            />
-          }
-        >
-          <Toggle
-            checked={Boolean(company?.settings.pdf_email_attachment)}
-            onValueChange={(value) =>
-              handleChange('settings.pdf_email_attachment', value)
-            }
-            disabled={disableSettingsField('pdf_email_attachment')}
-          />
-        </Element>
-
-        <Element
-          leftSide={
-            <PropertyCheckbox
-              propertyKey="document_email_attachment"
-              labelElement={<SettingsLabel label={t('attach_documents')} />}
-            />
-          }
-        >
-          <Toggle
-            checked={Boolean(company?.settings.document_email_attachment)}
-            onValueChange={(value) =>
-              handleChange('settings.document_email_attachment', value)
+    <>
+      <Settings
+        title={t('email_settings')}
+        docsLink="en/advanced-settings/#email_settings"
+        breadcrumbs={pages}
+        onSaveClick={handleOnSaveClick}
+        onCancelClick={onCancel}
+        disableSaveButton={showPlanAlert}
+      >
+        <AdvancedSettingsPlanAlert />
+
+        <Card title={t('settings')}>
+          <Element
+            leftSide={
+              <PropertyCheckbox
+                propertyKey="show_email_footer"
+                labelElement={<SettingsLabel label={t('show_email_footer')} />}
+              />
             }
-            disabled={disableSettingsField('document_email_attachment')}
-          />
-        </Element>
-
-        <Element
-          leftSide={
-            <PropertyCheckbox
-              propertyKey="ubl_email_attachment"
-              labelElement={
-                <SettingsLabel
-                  label={t('attach_ubl')}
-                  helpLabel={reactStringReplace(
-                    `${t('ubl_email_attachment_help')}.`,
-                    ':here',
-                    () => (
-                      <Link className="text-xs" to="/settings/e_invoice">
-                        {t('here')}
-                      </Link>
-                    )
-                  )}
-                />
+          >
+            <Toggle
+              checked={Boolean(company?.settings.show_email_footer)}
+              onValueChange={(value) =>
+                handleChange('settings.show_email_footer', value)
               }
+              disabled={disableSettingsField('show_email_footer')}
             />
-          }
-        >
-          <Toggle
-            checked={Boolean(company?.settings.ubl_email_attachment)}
-            onValueChange={(value) =>
-              handleChange('settings.ubl_email_attachment', value)
+          </Element>
+
+          <Element
+            leftSide={
+              <PropertyCheckbox
+                propertyKey="pdf_email_attachment"
+                labelElement={<SettingsLabel label={t('attach_pdf')} />}
+              />
             }
-            disabled={disableSettingsField('ubl_email_attachment')}
-          />
-        </Element>
-
-        <Divider />
-
-        <Element
-          leftSide={
-            <PropertyCheckbox
-              propertyKey="email_sending_method"
-              labelElement={<SettingsLabel label={t('email_provider')} />}
-              defaultValue="default"
+          >
+            <Toggle
+              checked={Boolean(company?.settings.pdf_email_attachment)}
+              onValueChange={(value) =>
+                handleChange('settings.pdf_email_attachment', value)
+              }
+              disabled={disableSettingsField('pdf_email_attachment')}
             />
-          }
-        >
-          <SelectField
-            value={company?.settings.email_sending_method || 'default'}
-            onValueChange={(value) =>
-              handleChange('settings.email_sending_method', value)
-            }
-            disabled={
-              disableSettingsField('email_sending_method') ||
-              (!proPlan() && !enterprisePlan())
+          </Element>
+
+          <Element
+            leftSide={
+              <PropertyCheckbox
+                propertyKey="document_email_attachment"
+                labelElement={<SettingsLabel label={t('attach_documents')} />}
+              />
             }
-            errorMessage={errors?.errors['settings.email_sending_method']}
           >
-            {emailProviders.map(
-              ({ value, label, enabled }) =>
-                enabled && (
-                  <option key={value} value={value}>
-                    {label}
-                  </option>
-                )
-            )}
-          </SelectField>
-        </Element>
-
-        {(company?.settings.email_sending_method === 'office365' ||
-          company?.settings.email_sending_method === 'microsoft' ||
-          company?.settings.email_sending_method === 'gmail') &&
-          isHosted() && (
-            <Element
-              leftSide={
-                <PropertyCheckbox
-                  propertyKey="gmail_sending_user_id"
-                  labelElement={
-                    <SettingsLabel label={`Gmail / Microsoft ${t('user')}`} />
-                  }
-                />
+            <Toggle
+              checked={Boolean(company?.settings.document_email_attachment)}
+              onValueChange={(value) =>
+                handleChange('settings.document_email_attachment', value)
               }
-            >
-              <UserSelector
-                endpoint="/api/v1/users?sending_users=true"
-                value={company?.settings?.gmail_sending_user_id}
-                onChange={(user) =>
-                  handleChange('settings.gmail_sending_user_id', user.id)
-                }
-                onClearButtonClick={() =>
-                  handleChange('settings.gmail_sending_user_id', '0')
+              disabled={disableSettingsField('document_email_attachment')}
+            />
+          </Element>
+
+          <Element
+            leftSide={
+              <PropertyCheckbox
+                propertyKey="ubl_email_attachment"
+                labelElement={
+                  <SettingsLabel
+                    label={t('attach_ubl')}
+                    helpLabel={reactStringReplace(
+                      `${t('ubl_email_attachment_help')}.`,
+                      ':here',
+                      () => (
+                        <Link className="text-xs" to="/settings/e_invoice">
+                          {t('here')}
+                        </Link>
+                      )
+                    )}
+                  />
                 }
-                readonly={disableSettingsField('gmail_sending_user_id')}
-                withoutAction
-                errorMessage={errors?.errors['settings.gmail_sending_user_id']}
               />
-            </Element>
-          )}
+            }
+          >
+            <Toggle
+              checked={Boolean(company?.settings.ubl_email_attachment)}
+              onValueChange={(value) =>
+                handleChange('settings.ubl_email_attachment', value)
+              }
+              disabled={disableSettingsField('ubl_email_attachment')}
+            />
+          </Element>
+
+          <Divider />
 
-        {company?.settings.email_sending_method === 'client_postmark' && (
           <Element
             leftSide={
               <PropertyCheckbox
-                propertyKey="postmark_secret"
-                labelElement={<SettingsLabel label={t('secret')} />}
+                propertyKey="email_sending_method"
+                labelElement={<SettingsLabel label={t('email_provider')} />}
+                defaultValue="default"
               />
             }
           >
-            <InputField
-              value={company?.settings.postmark_secret || ''}
+            <SelectField
+              value={company?.settings.email_sending_method || 'default'}
               onValueChange={(value) =>
-                handleChange('settings.postmark_secret', value)
+                handleChange('settings.email_sending_method', value)
               }
-              disabled={disableSettingsField('postmark_secret')}
-              errorMessage={errors?.errors['settings.postmark_secret']}
-            />
+              disabled={
+                disableSettingsField('email_sending_method') ||
+                (!proPlan() && !enterprisePlan())
+              }
+              errorMessage={errors?.errors['settings.email_sending_method']}
+            >
+              {emailProviders.map(
+                ({ value, label, enabled }) =>
+                  enabled && (
+                    <option key={value} value={value}>
+                      {label}
+                    </option>
+                  )
+              )}
+            </SelectField>
           </Element>
-        )}
 
-        {company?.settings.email_sending_method === 'client_mailgun' && (
-          <>
+          {(company?.settings.email_sending_method === 'office365' ||
+            company?.settings.email_sending_method === 'microsoft' ||
+            company?.settings.email_sending_method === 'gmail') &&
+            isHosted() && (
+              <Element
+                leftSide={
+                  <PropertyCheckbox
+                    propertyKey="gmail_sending_user_id"
+                    labelElement={
+                      <SettingsLabel label={`Gmail / Microsoft ${t('user')}`} />
+                    }
+                  />
+                }
+              >
+                <UserSelector
+                  endpoint="/api/v1/users?sending_users=true"
+                  value={company?.settings?.gmail_sending_user_id}
+                  onChange={(user) =>
+                    handleChange('settings.gmail_sending_user_id', user.id)
+                  }
+                  onClearButtonClick={() =>
+                    handleChange('settings.gmail_sending_user_id', '0')
+                  }
+                  readonly={disableSettingsField('gmail_sending_user_id')}
+                  withoutAction
+                  errorMessage={
+                    errors?.errors['settings.gmail_sending_user_id']
+                  }
+                />
+              </Element>
+            )}
+
+          {company?.settings.email_sending_method === 'client_postmark' && (
             <Element
               leftSide={
                 <PropertyCheckbox
-                  propertyKey="mailgun_secret"
+                  propertyKey="postmark_secret"
                   labelElement={<SettingsLabel label={t('secret')} />}
                 />
               }
             >
               <InputField
-                value={company?.settings.mailgun_secret || ''}
+                value={company?.settings.postmark_secret || ''}
                 onValueChange={(value) =>
-                  handleChange('settings.mailgun_secret', value)
+                  handleChange('settings.postmark_secret', value)
                 }
-                disabled={disableSettingsField('mailgun_secret')}
-                errorMessage={errors?.errors['settings.mailgun_secret']}
+                disabled={disableSettingsField('postmark_secret')}
+                errorMessage={errors?.errors['settings.postmark_secret']}
               />
             </Element>
+          )}
+
+          {company?.settings.email_sending_method === 'client_mailgun' && (
+            <>
+              <Element
+                leftSide={
+                  <PropertyCheckbox
+                    propertyKey="mailgun_secret"
+                    labelElement={<SettingsLabel label={t('secret')} />}
+                  />
+                }
+              >
+                <InputField
+                  value={company?.settings.mailgun_secret || ''}
+                  onValueChange={(value) =>
+                    handleChange('settings.mailgun_secret', value)
+                  }
+                  disabled={disableSettingsField('mailgun_secret')}
+                  errorMessage={errors?.errors['settings.mailgun_secret']}
+                />
+              </Element>
 
+              <Element
+                leftSide={
+                  <PropertyCheckbox
+                    propertyKey="mailgun_domain"
+                    labelElement={<SettingsLabel label={t('domain')} />}
+                  />
+                }
+              >
+                <InputField
+                  value={company?.settings.mailgun_domain || ''}
+                  onValueChange={(value) =>
+                    handleChange('settings.mailgun_domain', value)
+                  }
+                  disabled={disableSettingsField('mailgun_domain')}
+                  errorMessage={errors?.errors['settings.mailgun_domain']}
+                />
+              </Element>
+
+              <Element
+                leftSide={
+                  <PropertyCheckbox
+                    propertyKey="mailgun_endpoint"
+                    labelElement={<SettingsLabel label={t('endpoint')} />}
+                    defaultValue="api.mailgun.net"
+                  />
+                }
+              >
+                <SelectField
+                  value={
+                    company?.settings.mailgun_endpoint || 'api.mailgun.net'
+                  }
+                  onValueChange={(value) =>
+                    handleChange('settings.mailgun_endpoint', value)
+                  }
+                  disabled={disableSettingsField('mailgun_endpoint')}
+                  errorMessage={errors?.errors['settings.mailgun_endpoint']}
+                >
+                  <option value="api.mailgun.net" defaultChecked>
+                    api.mailgun.net
+                  </option>
+                  <option value="api.eu.mailgun.net">api.eu.mailgun.net</option>
+                </SelectField>
+              </Element>
+            </>
+          )}
+
+          {company?.settings.email_sending_method === 'client_brevo' && (
             <Element
               leftSide={
                 <PropertyCheckbox
-                  propertyKey="mailgun_domain"
-                  labelElement={<SettingsLabel label={t('domain')} />}
+                  propertyKey="brevo_secret"
+                  labelElement={<SettingsLabel label={t('secret')} />}
                 />
               }
             >
               <InputField
-                value={company?.settings.mailgun_domain || ''}
+                value={company?.settings.brevo_secret || ''}
                 onValueChange={(value) =>
-                  handleChange('settings.mailgun_domain', value)
+                  handleChange('settings.brevo_secret', value)
                 }
-                disabled={disableSettingsField('mailgun_domain')}
-                errorMessage={errors?.errors['settings.mailgun_domain']}
+                disabled={disableSettingsField('brevo_secret')}
+                errorMessage={errors?.errors['settings.brevo_secret']}
               />
             </Element>
+          )}
 
+          {(company?.settings.email_sending_method === 'client_mailgun' ||
+            company?.settings.email_sending_method === 'client_postmark' ||
+            company?.settings.email_sending_method === 'smtp' ||
+            company?.settings.email_sending_method === 'client_brevo') && (
             <Element
               leftSide={
                 <PropertyCheckbox
-                  propertyKey="mailgun_endpoint"
-                  labelElement={<SettingsLabel label={t('endpoint')} />}
-                  defaultValue="api.mailgun.net"
+                  propertyKey="custom_sending_email"
+                  labelElement={<SettingsLabel label={t('from_email')} />}
                 />
               }
             >
-              <SelectField
-                value={company?.settings.mailgun_endpoint || 'api.mailgun.net'}
+              <InputField
+                value={company?.settings.custom_sending_email || ''}
                 onValueChange={(value) =>
-                  handleChange('settings.mailgun_endpoint', value)
+                  handleChange('settings.custom_sending_email', value)
                 }
-                disabled={disableSettingsField('mailgun_endpoint')}
-                errorMessage={errors?.errors['settings.mailgun_endpoint']}
-              >
-                <option value="api.mailgun.net" defaultChecked>
-                  api.mailgun.net
-                </option>
-                <option value="api.eu.mailgun.net">api.eu.mailgun.net</option>
-              </SelectField>
+                disabled={disableSettingsField('custom_sending_email')}
+                errorMessage={errors?.errors['settings.custom_sending_email']}
+              />
             </Element>
-          </>
-        )}
+          )}
 
-        {company?.settings.email_sending_method === 'client_brevo' && (
           <Element
             leftSide={
               <PropertyCheckbox
-                propertyKey="brevo_secret"
-                labelElement={<SettingsLabel label={t('secret')} />}
+                propertyKey="email_from_name"
+                labelElement={<SettingsLabel label={t('from_name')} />}
               />
             }
           >
             <InputField
-              value={company?.settings.brevo_secret || ''}
+              value={company?.settings.email_from_name || ''}
               onValueChange={(value) =>
-                handleChange('settings.brevo_secret', value)
+                handleChange('settings.email_from_name', value)
               }
-              disabled={disableSettingsField('brevo_secret')}
-              errorMessage={errors?.errors['settings.brevo_secret']}
+              disabled={disableSettingsField('email_from_name')}
+              errorMessage={errors?.errors['settings.email_from_name']}
             />
           </Element>
-        )}
 
-        {(company?.settings.email_sending_method === 'client_mailgun' ||
-          company?.settings.email_sending_method === 'client_postmark' ||
-          company?.settings.email_sending_method === 'smtp' ||
-          company?.settings.email_sending_method === 'client_brevo') && (
           <Element
             leftSide={
               <PropertyCheckbox
-                propertyKey="custom_sending_email"
-                labelElement={<SettingsLabel label={t('from_email')} />}
+                propertyKey="reply_to_name"
+                labelElement={<SettingsLabel label={t('reply_to_name')} />}
               />
             }
           >
             <InputField
-              value={company?.settings.custom_sending_email || ''}
+              value={company?.settings.reply_to_name || ''}
               onValueChange={(value) =>
-                handleChange('settings.custom_sending_email', value)
+                handleChange('settings.reply_to_name', value)
               }
-              disabled={disableSettingsField('custom_sending_email')}
-              errorMessage={errors?.errors['settings.custom_sending_email']}
+              disabled={disableSettingsField('reply_to_name')}
+              errorMessage={errors?.errors['settings.reply_to_name']}
             />
           </Element>
-        )}
 
-        <Element
-          leftSide={
-            <PropertyCheckbox
-              propertyKey="email_from_name"
-              labelElement={<SettingsLabel label={t('from_name')} />}
-            />
-          }
-        >
-          <InputField
-            value={company?.settings.email_from_name || ''}
-            onValueChange={(value) =>
-              handleChange('settings.email_from_name', value)
-            }
-            disabled={disableSettingsField('email_from_name')}
-            errorMessage={errors?.errors['settings.email_from_name']}
-          />
-        </Element>
-
-        <Element
-          leftSide={
-            <PropertyCheckbox
-              propertyKey="reply_to_name"
-              labelElement={<SettingsLabel label={t('reply_to_name')} />}
-            />
-          }
-        >
-          <InputField
-            value={company?.settings.reply_to_name || ''}
-            onValueChange={(value) =>
-              handleChange('settings.reply_to_name', value)
-            }
-            disabled={disableSettingsField('reply_to_name')}
-            errorMessage={errors?.errors['settings.reply_to_name']}
-          />
-        </Element>
-
-        <Element
-          leftSide={
-            <PropertyCheckbox
-              propertyKey="reply_to_email"
-              labelElement={<SettingsLabel label={t('reply_to_email')} />}
-            />
-          }
-        >
-          <InputField
-            value={company?.settings.reply_to_email || ''}
-            onValueChange={(value) =>
-              handleChange('settings.reply_to_email', value)
-            }
-            disabled={disableSettingsField('reply_to_email')}
-            errorMessage={errors?.errors['settings.reply_to_email']}
-          />
-        </Element>
-
-        {company?.settings.email_sending_method !== 'smtp' && (
           <Element
             leftSide={
               <PropertyCheckbox
-                propertyKey="bcc_email"
-                labelElement={
-                  <SettingsLabel
-                    label={t('bcc_email')}
-                    helpLabel={t('comma_sparated_list')}
-                  />
-                }
+                propertyKey="reply_to_email"
+                labelElement={<SettingsLabel label={t('reply_to_email')} />}
               />
             }
           >
             <InputField
-              value={company?.settings.bcc_email || ''}
+              value={company?.settings.reply_to_email || ''}
               onValueChange={(value) =>
-                handleChange('settings.bcc_email', value)
+                handleChange('settings.reply_to_email', value)
               }
-              disabled={disableSettingsField('bcc_email')}
-              errorMessage={errors?.errors['settings.bcc_email']}
+              disabled={disableSettingsField('reply_to_email')}
+              errorMessage={errors?.errors['settings.reply_to_email']}
             />
           </Element>
-        )}
 
-        {company?.settings.email_sending_method !== 'smtp' && (
+          {company?.settings.email_sending_method !== 'smtp' && (
+            <Element
+              leftSide={
+                <PropertyCheckbox
+                  propertyKey="bcc_email"
+                  labelElement={
+                    <SettingsLabel
+                      label={t('bcc_email')}
+                      helpLabel={t('comma_sparated_list')}
+                    />
+                  }
+                />
+              }
+            >
+              <InputField
+                value={company?.settings.bcc_email || ''}
+                onValueChange={(value) =>
+                  handleChange('settings.bcc_email', value)
+                }
+                disabled={disableSettingsField('bcc_email')}
+                errorMessage={errors?.errors['settings.bcc_email']}
+              />
+            </Element>
+          )}
+
+          {company?.settings.email_sending_method !== 'smtp' && (
+            <Element
+              leftSide={
+                <PropertyCheckbox
+                  propertyKey="entity_send_time"
+                  labelElement={<SettingsLabel label={t('send_time')} />}
+                />
+              }
+            >
+              <SelectField
+                value={company?.settings.entity_send_time || ''}
+                onValueChange={(value) =>
+                  handleChange(
+                    'settings.entity_send_time',
+                    value.length > 0 ? value : 6
+                  )
+                }
+                withBlank
+                disabled={disableSettingsField('entity_send_time')}
+                errorMessage={errors?.errors['settings.entity_send_time']}
+              >
+                {[...Array(24).keys()].map((number, index) => (
+                  <option key={index} value={number + 1}>
+                    {dayjs()
+                      .startOf('day')
+                      .add(number + 1, 'hour')
+                      .format('h:ss A')}
+                  </option>
+                ))}
+              </SelectField>
+            </Element>
+          )}
+
+          {company?.settings.email_sending_method === 'smtp' && (
+            <SMTPMailDriver />
+          )}
+
+          <Divider />
+
           <Element
             leftSide={
               <PropertyCheckbox
-                propertyKey="entity_send_time"
-                labelElement={<SettingsLabel label={t('send_time')} />}
+                propertyKey="email_style"
+                labelElement={<SettingsLabel label={t('email_design')} />}
+                defaultValue="plain"
               />
             }
           >
             <SelectField
-              value={company?.settings.entity_send_time || ''}
+              value={company?.settings.email_style || 'plain'}
               onValueChange={(value) =>
-                handleChange(
-                  'settings.entity_send_time',
-                  value.length > 0 ? value : 6
-                )
+                handleChange('settings.email_style', value)
               }
-              withBlank
-              disabled={disableSettingsField('entity_send_time')}
-              errorMessage={errors?.errors['settings.entity_send_time']}
+              disabled={disableSettingsField('email_style')}
+              errorMessage={errors?.errors['settings.email_style']}
             >
-              {[...Array(24).keys()].map((number, index) => (
-                <option key={index} value={number + 1}>
-                  {dayjs()
-                    .startOf('day')
-                    .add(number + 1, 'hour')
-                    .format('h:ss A')}
-                </option>
-              ))}
+              <option value="plain">{t('plain')}</option>
+              <option value="light">{t('light')}</option>
+              <option value="dark">{t('dark')}</option>
+              <option value="custom">{t('custom')}</option>
             </SelectField>
           </Element>
-        )}
-
-        {company?.settings.email_sending_method === 'smtp' && (
-          <SMTPMailDriver />
-        )}
 
-        <Divider />
+          {company?.settings.email_style === 'custom' && (
+            <Element
+              leftSide={
+                <PropertyCheckbox
+                  propertyKey="email_style_custom"
+                  labelElement={<SettingsLabel label={t('custom')} />}
+                />
+              }
+            >
+              <InputField
+                element="textarea"
+                value={company?.settings.email_style_custom || ''}
+                onValueChange={(value) =>
+                  value.includes('$body')
+                    ? handleChange('settings.email_style_custom', value)
+                    : toast.error(
+                        trans('body_variable_missing', { body: '$body' })
+                      )
+                }
+                disabled={disableSettingsField('email_style_custom')}
+                errorMessage={errors?.errors['settings.email_style_custom']}
+              />
+            </Element>
+          )}
 
-        <Element
-          leftSide={
-            <PropertyCheckbox
-              propertyKey="email_style"
-              labelElement={<SettingsLabel label={t('email_design')} />}
-              defaultValue="plain"
-            />
-          }
-        >
-          <SelectField
-            value={company?.settings.email_style || 'plain'}
-            onValueChange={(value) =>
-              handleChange('settings.email_style', value)
-            }
-            disabled={disableSettingsField('email_style')}
-            errorMessage={errors?.errors['settings.email_style']}
-          >
-            <option value="plain">{t('plain')}</option>
-            <option value="light">{t('light')}</option>
-            <option value="dark">{t('dark')}</option>
-            <option value="custom">{t('custom')}</option>
-          </SelectField>
-        </Element>
-
-        {company?.settings.email_style === 'custom' && (
           <Element
             leftSide={
               <PropertyCheckbox
-                propertyKey="email_style_custom"
-                labelElement={<SettingsLabel label={t('custom')} />}
+                propertyKey="email_signature"
+                labelElement={<SettingsLabel label={t('signature')} />}
               />
             }
           >
-            <InputField
-              element="textarea"
-              value={company?.settings.email_style_custom || ''}
-              onValueChange={(value) =>
-                value.includes('$body')
-                  ? handleChange('settings.email_style_custom', value)
-                  : toast.error(
-                      trans('body_variable_missing', { body: '$body' })
-                    )
+            <MarkdownEditor
+              value={company?.settings.email_signature || ''}
+              onChange={(value) =>
+                handleChange('settings.email_signature', value)
               }
-              disabled={disableSettingsField('email_style_custom')}
-              errorMessage={errors?.errors['settings.email_style_custom']}
+              disabled={disableSettingsField('email_signature')}
             />
           </Element>
-        )}
-
-        <Element
-          leftSide={
-            <PropertyCheckbox
-              propertyKey="email_signature"
-              labelElement={<SettingsLabel label={t('signature')} />}
-            />
-          }
-        >
-          <MarkdownEditor
-            value={company?.settings.email_signature || ''}
-            onChange={(value) =>
-              handleChange('settings.email_signature', value)
-            }
-            disabled={disableSettingsField('email_signature')}
-          />
-        </Element>
-      </Card>
-    </Settings>
+        </Card>
+      </Settings>
+
+      <SendTimeModal
+        visible={isSendTimeModalOpen}
+        setVisible={setIsSendTimeModalOpen}
+        onConfirm={(syncEnabled) => {
+          onSave({
+            syncSendTime: syncEnabled,
+          });
+        }}
+      />
+    </>
   );
 }
diff --git a/src/pages/settings/email-settings/common/components/SendTimeModal.tsx b/src/pages/settings/email-settings/common/components/SendTimeModal.tsx
new file mode 100644
index 0000000000..7aa9864710
--- /dev/null
+++ b/src/pages/settings/email-settings/common/components/SendTimeModal.tsx
@@ -0,0 +1,70 @@
+/**
+ * Invoice Ninja (https://invoiceninja.com).
+ *
+ * @link https://github.com/invoiceninja/invoiceninja source repository
+ *
+ * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
+ *
+ * @license https://www.elastic.co/licensing/elastic-license
+ */
+
+import { Modal } from '$app/components/Modal';
+import { useTranslation } from 'react-i18next';
+import { Dispatch, SetStateAction, useState } from 'react';
+import { Button } from '$app/components/forms';
+import Toggle from '$app/components/forms/Toggle';
+import { Element } from '$app/components/cards';
+interface SendTimeModalProps {
+  visible: boolean;
+  setVisible: Dispatch<SetStateAction<boolean>>;
+  onConfirm: (syncEnabled: boolean) => void;
+}
+
+export function SendTimeModal({
+  visible,
+  setVisible,
+  onConfirm,
+}: SendTimeModalProps) {
+  const [t] = useTranslation();
+
+  const [syncEnabled, setSyncEnabled] = useState<boolean>(false);
+
+  const handleOnClose = () => {
+    setSyncEnabled(false);
+    setVisible(false);
+  };
+
+  return (
+    <Modal title={t('send_time')} visible={visible} onClose={handleOnClose}>
+      <div className="flex flex-col space-y-4">
+        <div className="flex flex-col space-y-2">
+          <Element
+            leftSide={t('sync_send_time')}
+            noExternalPadding
+            noVerticalPadding
+            withoutWrappingLeftSide
+          >
+            <Toggle
+              checked={syncEnabled}
+              onChange={() => setSyncEnabled(!syncEnabled)}
+            />
+          </Element>
+
+          <span className="text-sm text-gray-500">
+            {t('sync_send_time_help')}
+          </span>
+        </div>
+
+        <Button
+          behavior="button"
+          onClick={() => {
+            onConfirm(syncEnabled);
+            handleOnClose();
+          }}
+        >
+          {t('confirm')}
+        </Button>
+      </div>
+    </Modal>
+  );
+}
diff --git a/src/pages/settings/gateways/common/components/GatewaysTable.tsx b/src/pages/settings/gateways/common/components/GatewaysTable.tsx
index b0a5e9190d..39d402af49 100644
--- a/src/pages/settings/gateways/common/components/GatewaysTable.tsx
+++ b/src/pages/settings/gateways/common/components/GatewaysTable.tsx
@@ -167,7 +167,7 @@ export function GatewaysTable(params: Params) {
 
   useEffect(() => {
     if (updateCompany) {
-      handleCompanySave(updateCompany);
+      handleCompanySave({ excludeToasters: true });
 
       setUpdateCompany(false);
     }
diff --git a/src/pages/vendors/components/CreateVendorForm.tsx b/src/pages/vendors/components/CreateVendorForm.tsx
index 67da3857a7..825f6d7854 100644
--- a/src/pages/vendors/components/CreateVendorForm.tsx
+++ b/src/pages/vendors/components/CreateVendorForm.tsx
@@ -68,7 +68,7 @@ export function CreateVendorForm(props: Props) {
     set(vendor as Vendor, 'contacts', contacts);
     toast.processing();
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     request('POST', endpoint('/api/v1/vendors'), vendor)
       .then((response) => {
diff --git a/src/pages/vendors/edit/Edit.tsx b/src/pages/vendors/edit/Edit.tsx
index 384f8eb02e..d8464c1901 100644
--- a/src/pages/vendors/edit/Edit.tsx
+++ b/src/pages/vendors/edit/Edit.tsx
@@ -66,7 +66,7 @@ export default function Edit() {
     set(vendor as Vendor, 'contacts', contacts);
     toast.processing();
 
-    await saveCompany(true);
+    await saveCompany({ excludeToasters: true });
 
     request('PUT', endpoint('/api/v1/vendors/:id', { id }), vendor)
       .then(() => {


Reviews:
user: login: beganovich
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-04T12:10:45Z
user: login: Civolilah
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-04T13:50:32Z
user: login: beganovich
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-05T10:48:29Z

Review comments:
diff_hunk: @@ -167,7 +167,7 @@ export function GatewaysTable(params: Params) {
 
   useEffect(() => {
     if (updateCompany) {
-      handleCompanySave(updateCompany);
+      handleCompanySave({ excludeToasters: true });
path: src/pages/settings/gateways/common/components/GatewaysTable.tsx
user: login: beganovich
body: I wonder in which cases we want toasters? It seems like we're disabling tons of them, I wonder if opposite should be default? 
created_at: 2025-03-04T12:10:36Z
author_association: MEMBER
diff_hunk: @@ -167,7 +167,7 @@ export function GatewaysTable(params: Params) {
 
   useEffect(() => {
     if (updateCompany) {
-      handleCompanySave(updateCompany);
+      handleCompanySave({ excludeToasters: true });
path: src/pages/settings/gateways/common/components/GatewaysTable.tsx
user: login: Civolilah
body: @beganovich So, we are disabling the toaster for the company endpoint in cases when another "priority" endpoint is called on that page. This priority endpoint already includes great UX with its own toaster, so having the company endpoint toaster would potentially break the UX. In this specific case that you commented on, we're disabling the toaster when users drag and drop (dnd) gateways (prioritizing payments), so users can simply drag and drop while the details are saved in the background.
created_at: 2025-03-04T13:50:31Z
author_association: COLLABORATOR

PR comments:
user: login: Civolilah
created_at: 2025-03-01T18:56:47Z
body: @turbo124 As the API does not have this query parameter logic implemented, please let me know if you want to change anything in the implementation.
author_association: COLLABORATOR
user: login: turbo124
created_at: 2025-03-02T08:02:17Z
body: @Civolilah 

Thanks, we'll need to add a toggle to the confirmation modal. The Sync Send Time will need to be optionally turned on.

We'll need to use the value of the ?query parameter  `?sync_send_time=true` or `?sync_send_time=false`
author_association: MEMBER
user: login: Civolilah
created_at: 2025-03-02T18:42:53Z
body: > @Civolilah
> 
> Thanks, we'll need to add a toggle to the confirmation modal. The Sync Send Time will need to be optionally turned on.
> 
> We'll need to use the value of the ?query parameter `?sync_send_time=true` or `?sync_send_time=false`

@turbo124 Okay, I've adjusted the solution by adding a toggle in the modal. This is how I imagined the solution with a toggle. Screenshot:

<img width="1259" alt="Screenshot 2025-03-02 at 19 35 15" src="https://github.com/user-attachments/assets/794d71ca-63ef-4bc1-a1b5-1fa047cc8ef1" />

On the screenshot I used plain text, but the text is replaced with "sync_send_time" and "sync_send_time_help".

Let me know your thoughts.
author_association: COLLABORATOR