(frontend) add a button mode to the messages widget hook

The homepage needs the widget floating button while the help menu
opens the form directly. A WidgetHelper now owns the command queue
and script injection so both entry points share the same bootstrap,
and it fills the legacy queue key so older widget runtimes keep
working. The hook exposes showButton to init the loader script and
canLoadWidget so callers can check the config before rendering.
This commit is contained in:
Nathan Vasse
2026-07-28 09:41:07 +02:00
parent 9120c819ec
commit 26197545af
2 changed files with 86 additions and 39 deletions
@@ -1,6 +1,7 @@
import { useTranslation } from "react-i18next";
import { useAuth } from "../auth/Auth";
import { useConfig } from "../config/ConfigProvider";
import { WidgetHelper } from "./widget-helper";
/**
* Hook that opens the feedback widget
@@ -14,59 +15,71 @@ export const useMessagesWidget = () => {
const widgetPath = config?.FRONTEND_FEEDBACK_MESSAGES_WIDGET_PATH;
const channel = config?.FRONTEND_FEEDBACK_MESSAGES_WIDGET_CHANNEL;
const title: string = t("feedback_widget.title");
const placeholder: string = t("feedback_widget.placeholder");
const emailPlaceholder: string = t("feedback_widget.email_placeholder");
const submitText: string = t("feedback_widget.submit_text");
const successText: string = t("feedback_widget.success_text");
const successText2: string = t("feedback_widget.success_text2");
const getWidgetConfig = () => {
const title: string = t("feedback_widget.title");
const placeholder: string = t("feedback_widget.placeholder");
const emailPlaceholder: string = t("feedback_widget.email_placeholder");
const submitText: string = t("feedback_widget.submit_text");
const successText: string = t("feedback_widget.success_text");
const successText2: string = t("feedback_widget.success_text2");
return {
title,
placeholder,
emailPlaceholder,
submitText,
successText,
successText2,
};
};
const canLoadWidget = Boolean(channel && apiUrl && widgetPath);
const showWidget = () => {
if (!channel || !apiUrl || !widgetPath) {
if (!canLoadWidget) {
throw new Error(
"FRONTEND_FEEDBACK_MESSAGES_WIDGET_API_URL, FRONTEND_FEEDBACK_MESSAGES_WIDGET_PATH or FRONTEND_FEEDBACK_MESSAGES_WIDGET_CHANNEL is not set"
"FRONTEND_FEEDBACK_MESSAGES_WIDGET_API_URL, FRONTEND_FEEDBACK_MESSAGES_WIDGET_PATH or FRONTEND_FEEDBACK_MESSAGES_WIDGET_CHANNEL is not set",
);
}
// Initialize the widget array if it doesn't exist
if (typeof window !== "undefined" && widgetPath) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any)._stmsg_widget = (window as any)._stmsg_widget || [];
WidgetHelper.pushCommand([
"feedback",
"init",
{
api: apiUrl,
channel,
...getWidgetConfig(),
// Add email parameter if user is logged in
...(user?.email && { email: user.email }),
},
]);
// Construct script URLs from the base path
const feedbackScript = `${widgetPath}feedback.js`;
WidgetHelper.loadScript(`${widgetPath}feedback.js`);
};
// Push the widget configuration
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any)._stmsg_widget.push([
"feedback",
"init",
{
title,
const showButton = () => {
if (!canLoadWidget) {
throw new Error(
"FRONTEND_FEEDBACK_MESSAGES_WIDGET_API_URL, FRONTEND_FEEDBACK_MESSAGES_WIDGET_PATH or FRONTEND_FEEDBACK_MESSAGES_WIDGET_CHANNEL is not set",
);
}
WidgetHelper.pushCommand([
"loader",
"init",
{
params: {
api: apiUrl,
channel,
placeholder,
emailPlaceholder,
submitText,
successText,
successText2,
...getWidgetConfig(),
// Add email parameter if user is logged in
...(user?.email && { email: user.email }),
},
]);
script: `${widgetPath}feedback.js`,
},
]);
// Load the loader script if not already loaded
if (!document.querySelector(`script[src="${feedbackScript}"]`)) {
const script = document.createElement("script");
script.async = true;
script.src = feedbackScript;
const firstScript = document.getElementsByTagName("script")[0];
if (firstScript && firstScript.parentNode) {
firstScript.parentNode.insertBefore(script, firstScript);
}
}
}
WidgetHelper.loadScript(`${widgetPath}loader.js`);
};
return { showWidget };
return { showWidget, showButton, canLoadWidget };
};
@@ -0,0 +1,34 @@
// Widget helper functions to share common logic
declare global {
interface Window {
_lasuite_widget?: unknown[];
_stmsg_widget?: unknown[];
}
}
export class WidgetHelper {
// Both keys are populated so that legacy and current widget runtimes
// can consume the same command queue.
static #QUEUE_KEYS = ["_lasuite_widget", "_stmsg_widget"] as const;
static pushCommand(command: unknown[]) {
for (const key of WidgetHelper.#QUEUE_KEYS) {
const queue = window[key] ?? [];
queue.push(command);
window[key] = queue;
}
}
static loadScript(scriptUrl: string) {
if (document.querySelector(`script[src="${scriptUrl}"]`)) return;
const script = document.createElement("script");
script.async = true;
script.src = scriptUrl;
const firstScript = document.getElementsByTagName("script")[0];
if (firstScript && firstScript.parentNode) {
firstScript.parentNode.insertBefore(script, firstScript);
}
}
}