diff --git a/components/layout/__tests__/sidebar-vacation-banner.test.tsx b/components/layout/__tests__/sidebar-vacation-banner.test.tsx
index 1d2c8ff25..a34168eed 100644
--- a/components/layout/__tests__/sidebar-vacation-banner.test.tsx
+++ b/components/layout/__tests__/sidebar-vacation-banner.test.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { fireEvent, render, screen } from '@testing-library/react';
+import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
// "Vacation responder is active" opens Settings > Out of Office, not
@@ -21,10 +21,13 @@ vi.mock('@/hooks/use-mailbox-drop', () => ({
}));
vi.mock('@/hooks/use-tag-drop', () => ({ useTagDrop: () => ({ dropHandlers: {}, isValidDropTarget: false }) }));
vi.mock('../account-switcher', () => ({ AccountSwitcher: () => null }));
+const toast = vi.hoisted(() => ({ error: vi.fn(), success: vi.fn(), info: vi.fn(), progress: vi.fn(), dismiss: vi.fn() }));
+vi.mock('@/stores/toast-store', () => ({ toast }));
const { Sidebar } = await import('../sidebar');
const { useVacationStore } = await import('@/stores/vacation-store');
const { buildSettingsPath } = await import('@/lib/deep-links');
+const { useAuthStore } = await import('@/stores/auth-store');
describe('vacation banner', () => {
it('opens the Out of Office settings', () => {
@@ -34,4 +37,25 @@ describe('vacation banner', () => {
expect(nav.push).toHaveBeenCalledWith(buildSettingsPath('vacation'));
expect(buildSettingsPath('vacation')).not.toBe('/settings');
});
+
+ // settings-p2-12: the banner says "Out of office is on", as the setting is
+ // called, and turns it off without a trip to Settings, like Gmail's "End now".
+ it('turns out of office off on the server', async () => {
+ const update = vi.fn(async () => true);
+ useAuthStore.setState({ client: {} as never });
+ useVacationStore.setState({ isEnabled: true, isSupported: true, updateVacationResponse: update } as never);
+ render();
+ fireEvent.click(screen.getByRole('button', { name: 'vacation_turn_off' }));
+ await waitFor(() => expect(update).toHaveBeenCalledWith(expect.anything(), { isEnabled: false }, undefined));
+ expect(toast.error).not.toHaveBeenCalled();
+ });
+
+ it('says so when turning it off fails', async () => {
+ const update = vi.fn(async () => { throw new Error('down'); });
+ useAuthStore.setState({ client: {} as never });
+ useVacationStore.setState({ isEnabled: true, isSupported: true, updateVacationResponse: update } as never);
+ render();
+ fireEvent.click(screen.getByRole('button', { name: 'vacation_turn_off' }));
+ await waitFor(() => expect(toast.error).toHaveBeenCalledWith('vacation_save_failed'));
+ });
});
diff --git a/components/layout/sidebar.tsx b/components/layout/sidebar.tsx
index aaf3672e5..3b41225c1 100644
--- a/components/layout/sidebar.tsx
+++ b/components/layout/sidebar.tsx
@@ -76,6 +76,7 @@ import { sidebarFolded, useUIStore } from "@/stores/ui-store";
import { useAuthStore } from "@/stores/auth-store";
import { unifiedLoadProgress } from "@/lib/unified-mailbox";
import { useVacationStore } from "@/stores/vacation-store";
+import { useManagedAccountStore } from "@/stores/managed-account-store";
import { useSettingsStore, getKeywordVisibility } from "@/stores/settings-store";
import { usePolicyStore } from "@/stores/policy-store";
import { useEmailStore, buildTagViewAccountClients } from "@/stores/email-store";
@@ -1070,26 +1071,59 @@ function DemoBanner({ ruled }: { ruled: boolean }) {
);
}
-function VacationBanner() {
+export function VacationBanner() {
const t = useTranslations('sidebar');
+ const tNotifications = useTranslations('notifications');
+ const tFilters = useTranslations('settings.filters');
const router = useRouter();
- const { isEnabled, isSupported } = useVacationStore(useShallow((state) => ({ isEnabled: state.isEnabled, isSupported: state.isSupported })));
+ const { isEnabled, isSupported, isSaving } = useVacationStore(useShallow((state) => ({
+ isEnabled: state.isEnabled, isSupported: state.isSupported, isSaving: state.isSaving,
+ })));
+ // The store holds the reply of the account Settings last loaded it for.
+ const managedAccountId = useManagedAccountStore((s) => s.managedAccountId);
+ const client = useAuthStore((s) => s.client);
+ const [turningOff, setTurningOff] = useState(false);
if (!isSupported || !isEnabled) return null;
+ // Like Gmail's "End now": off on the server, without opening Settings.
+ const turnOff = async () => {
+ if (!client || turningOff) return;
+ setTurningOff(true);
+ try {
+ await useVacationStore.getState().updateVacationResponse(client, { isEnabled: false }, managedAccountId ?? undefined);
+ } catch {
+ toast.error(tNotifications('vacation_save_failed'));
+ } finally {
+ setTurningOff(false);
+ }
+ };
+
return (
-