diff --git a/src/frontend/apps/drive/src/features/drivers/types.ts b/src/frontend/apps/drive/src/features/drivers/types.ts index 0d1782c1..55000b24 100644 --- a/src/frontend/apps/drive/src/features/drivers/types.ts +++ b/src/frontend/apps/drive/src/features/drivers/types.ts @@ -222,6 +222,7 @@ export type ApiConfig = { legalNoticeUrl?: string; }; supportEmail?: string; + supportMessagesWidget?: boolean; }; FRONTEND_THEME?: string; FRONTEND_HIDE_GAUFRE?: boolean; diff --git a/src/frontend/apps/drive/src/features/layouts/components/explorer/ExplorerLayout.tsx b/src/frontend/apps/drive/src/features/layouts/components/explorer/ExplorerLayout.tsx index ad4b07b4..53bdd189 100644 --- a/src/frontend/apps/drive/src/features/layouts/components/explorer/ExplorerLayout.tsx +++ b/src/frontend/apps/drive/src/features/layouts/components/explorer/ExplorerLayout.tsx @@ -43,6 +43,7 @@ import i18n from "@/features/i18n/initI18n"; import { useMemo } from "react"; import { UserProfile } from "@/features/ui/components/user/UserProfile"; import { Gaufre } from "@/features/ui/components/gaufre/Gaufre"; +import { useMessagesWidget } from "@/features/feedback/useMessagesWidget"; export const getGlobalExplorerLayout = (page: React.ReactElement) => { return {page}; @@ -199,20 +200,26 @@ const HelpMenuButton = () => { const helpMenuConfig = config?.FRONTEND_HELP_MENU_CONFIG; const hasHelpMenu = !!helpMenuConfig && Object.keys(helpMenuConfig).length > 0; + const { showWidget } = useMessagesWidget(); if (!hasHelpMenu) { return null; } + const getOnContactUs = () => { + if (helpMenuConfig.supportMessagesWidget) { + return () => showWidget(); + } + return helpMenuConfig.supportEmail + ? () => window.open(helpMenuConfig.supportEmail) + : undefined; + }; + return ( window.open(helpMenuConfig.supportEmail) - : undefined - } + onContactUs={getOnContactUs()} /> ); }; diff --git a/src/frontend/apps/e2e/__tests__/app-drive/help-menu.spec.ts b/src/frontend/apps/e2e/__tests__/app-drive/help-menu.spec.ts index 66a7bead..1bee8c2b 100644 --- a/src/frontend/apps/e2e/__tests__/app-drive/help-menu.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-drive/help-menu.spec.ts @@ -12,6 +12,7 @@ const HELP_MENU_CONFIG = { const overrideHelpMenuConfig = async ( page: import("@playwright/test").Page, helpMenuConfig: unknown, + extraConfig: Record = {}, ) => { await page.route("**/api/v1.0/config/", async (route) => { const response = await route.fetch(); @@ -21,10 +22,25 @@ const overrideHelpMenuConfig = async ( } else { json.FRONTEND_HELP_MENU_CONFIG = helpMenuConfig; } + Object.assign(json, extraConfig); await route.fulfill({ response, json }); }); }; +// Records window.open calls so that mailto links can be asserted without +// relying on the browser actually opening them. +const spyOnWindowOpen = async (page: import("@playwright/test").Page) => { + await page.addInitScript(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).__openedUrls = []; + window.open = (url?: string | URL) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).__openedUrls.push(String(url)); + return null; + }; + }); +}; + test.describe("Help menu", () => { test("renders the help menu with the configured options", async ({ page, @@ -60,6 +76,69 @@ test.describe("Help menu", () => { ).toHaveCount(0); }); + test("opens the support email when clicking on contact us", async ({ + page, + }) => { + await overrideHelpMenuConfig(page, HELP_MENU_CONFIG); + await spyOnWindowOpen(page); + await login(page, "drive@example.com"); + await page.goto("/"); + + const footer = page.locator(".c__left-panel__footer__drive"); + await footer.getByRole("button", { name: "Help" }).click(); + await page.getByRole("menuitem", { name: "Contact us" }).click(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const openedUrls = await page.evaluate(() => (window as any).__openedUrls); + expect(openedUrls).toEqual([HELP_MENU_CONFIG.supportEmail]); + }); + + test("opens the messages widget when clicking on contact us", async ({ + page, + }) => { + const widgetPath = "https://widget.example.com/"; + await overrideHelpMenuConfig( + page, + { + ...HELP_MENU_CONFIG, + supportMessagesWidget: true, + }, + { + FRONTEND_FEEDBACK_MESSAGES_WIDGET_API_URL: "https://widget.example.com/api/", + FRONTEND_FEEDBACK_MESSAGES_WIDGET_PATH: widgetPath, + FRONTEND_FEEDBACK_MESSAGES_WIDGET_CHANNEL: "drive", + }, + ); + // Serve an empty widget script so the injected loader does not hit the + // network for real. + await page.route(`${widgetPath}feedback.js`, (route) => + route.fulfill({ contentType: "application/javascript", body: "" }), + ); + await spyOnWindowOpen(page); + await login(page, "drive@example.com"); + await page.goto("/"); + + const footer = page.locator(".c__left-panel__footer__drive"); + await footer.getByRole("button", { name: "Help" }).click(); + await page.getByRole("menuitem", { name: "Contact us" }).click(); + + // The widget queues an init call and injects its loader script. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const widgetCalls = await page.evaluate(() => (window as any)._stmsg_widget); + expect(widgetCalls).toHaveLength(1); + expect(widgetCalls[0][0]).toBe("feedback"); + expect(widgetCalls[0][1]).toBe("init"); + expect(widgetCalls[0][2].channel).toBe("drive"); + await expect( + page.locator(`script[src="${widgetPath}feedback.js"]`), + ).toHaveCount(1); + + // The widget takes precedence over the support email. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const openedUrls = await page.evaluate(() => (window as any).__openedUrls); + expect(openedUrls).toEqual([]); + }); + test("does not render the help menu when the config is empty", async ({ page, }) => {