From e4109ef187f35d7e72a83153922d0d70112c7c01 Mon Sep 17 00:00:00 2001 From: Peter Szerzo Date: Tue, 30 Jul 2024 13:29:27 +0200 Subject: [PATCH] Persist nudge state --- packages/journey-manager/src/index.ts | 7 +- .../src/ui/components/index.tsx | 106 +++++++++++------- .../journey-manager/src/ui/custom-element.tsx | 11 ++ packages/journey-manager/src/ui/index.tsx | 2 + 4 files changed, 85 insertions(+), 41 deletions(-) diff --git a/packages/journey-manager/src/index.ts b/packages/journey-manager/src/index.ts index 11818b40..7687abd7 100644 --- a/packages/journey-manager/src/index.ts +++ b/packages/journey-manager/src/index.ts @@ -112,7 +112,12 @@ export const run = async (props: RunProps): Promise => { // ----- Set up UI ----- await dom.contentLoaded(); - const ui = createUi(props.ui, client, findActiveTriggers); + const ui = createUi( + props.config.conversationId, + props.ui, + client, + findActiveTriggers, + ); // --- Set up Digression detection --- const checkForDigressions = prepareDigression(triggers, () => { diff --git a/packages/journey-manager/src/ui/components/index.tsx b/packages/journey-manager/src/ui/components/index.tsx index 9e818340..1b6146e9 100644 --- a/packages/journey-manager/src/ui/components/index.tsx +++ b/packages/journey-manager/src/ui/components/index.tsx @@ -2,13 +2,7 @@ /* eslint-disable jsdoc/require-jsdoc */ import { type Client } from "@nlxai/multimodal"; import { type FunctionComponent as FC, type ComponentChildren } from "preact"; -import { - useEffect, - useState, - useRef, - type Dispatch, - type StateUpdater, -} from "preact/hooks"; +import { useEffect, useState, useRef } from "preact/hooks"; import { MultimodalIcon, SupportAgentIcon, @@ -266,58 +260,90 @@ const PinBubble: FC = ({ isActive, content, onClick }) => ( ); -const useOpenStateWithHistory = (): [ - boolean, - boolean, - Dispatch>, -] => { - const [isOpen, setIsOpen] = useState(false); +const nudgeStateLocalStorageKey = (conversationId: string): string => { + return `jb-nudge-state-${conversationId}`; +}; - const hasBeenOpened = useRef(false); +type NudgeState = "visible" | "hidden" | "dismissed"; - useEffect(() => { - if (isOpen) { - hasBeenOpened.current = true; - } - }, [isOpen]); +const saveNudgeState = ( + conversationId: string, + nudgeState: NudgeState, +): void => { + localStorage.setItem(nudgeStateLocalStorageKey(conversationId), nudgeState); +}; - return [isOpen, isOpen || hasBeenOpened.current, setIsOpen]; +const retrieveNudgeState = (conversationId: string): NudgeState => { + const val = localStorage.getItem(nudgeStateLocalStorageKey(conversationId)); + if (val === "visible") { + return "visible"; + } + if (val === "dismissed") { + return "dismissed"; + } + return "hidden"; }; export const ControlCenter: FC<{ config: UiConfig; + conversationId: string; client: Client; triggeredSteps: TriggeredStep[]; digression: boolean; highlightElements: HTMLElement[]; -}> = ({ config, client, triggeredSteps, highlightElements, digression }) => { - const [isOpen, hasBeenOpened, setIsOpen] = useOpenStateWithHistory(); +}> = ({ + config, + client, + conversationId, + triggeredSteps, + highlightElements, + digression, +}) => { + const [isOpen, setIsOpen] = useState(); const drawerContentRef = useRef(null); const drawerDialogRef = useRef(null); - const [isNudgeVisible, setIsNudgeVisible] = useState(false); + const [nudgeState, setNudgeState] = useState( + retrieveNudgeState(conversationId), + ); useEffect(() => { - if (hasBeenOpened || config.nudgeContent == null) { - setIsNudgeVisible(false); - return; + if (isOpen) { + setNudgeState("dismissed"); } + }, [isOpen]); - let hideTimeout: null | NodeJS.Timeout = null; - const showTimeout = setTimeout(() => { - setIsNudgeVisible(true); - hideTimeout = setTimeout(() => { - setIsNudgeVisible(false); - }, config.nudgeHideAfterMs ?? 20000); - }, config.nudgeShowAfterMs ?? 3000); - return () => { - clearTimeout(showTimeout); - if (hideTimeout) clearTimeout(hideTimeout); - }; + useEffect(() => { + saveNudgeState(conversationId, nudgeState); + }, [nudgeState, conversationId]); + + useEffect(() => { + if (config.nudgeContent == null) { + return; + } + if (nudgeState === "hidden") { + const showTimeout = setTimeout(() => { + setNudgeState("visible"); + }, config.nudgeShowAfterMs ?? 3000); + return () => { + clearTimeout(showTimeout); + }; + } else if (nudgeState === "visible") { + const hideTimeout = setTimeout( + () => { + setNudgeState("dismissed"); + }, + (config.nudgeHideAfterMs ?? 20000) - (config.nudgeShowAfterMs ?? 3000), + ); + return () => { + clearTimeout(hideTimeout); + }; + } }, [ config.nudgeContent, config.nudgeShowAfterMs, config.nudgeHideAfterMs, - hasBeenOpened, + nudgeState, + setNudgeState, ]); const onPreviousStep = config.onPreviousStep @@ -345,9 +371,9 @@ export const ControlCenter: FC<{ <> { - setIsNudgeVisible(false); + setNudgeState("dismissed"); }} content={config.nudgeContent ?? ""} /> diff --git a/packages/journey-manager/src/ui/custom-element.tsx b/packages/journey-manager/src/ui/custom-element.tsx index ae5218a9..a38f0f50 100644 --- a/packages/journey-manager/src/ui/custom-element.tsx +++ b/packages/journey-manager/src/ui/custom-element.tsx @@ -10,6 +10,7 @@ import type { UiConfig, TriggeredStep } from "../configuration"; export default class JourneyManagerElement extends HTMLElement { private _shadowRoot: ShadowRoot | null = null; private _client: Client | null = null; + private _conversationId: string | null = null; private _triggeredSteps: TriggeredStep[] | null = null; private _config: UiConfig | null = null; private _digression: boolean = false; @@ -41,6 +42,14 @@ export default class JourneyManagerElement extends HTMLElement { this.render(); } + /** + * Conversation ID + */ + set conversationId(value: string) { + this._conversationId = value; + this.render(); + } + /** * Set triggered steps */ @@ -65,6 +74,7 @@ export default class JourneyManagerElement extends HTMLElement { if ( this._config == null || this._client == null || + this._conversationId == null || this._triggeredSteps == null ) { return; @@ -74,6 +84,7 @@ export default class JourneyManagerElement extends HTMLElement { config={this._config} digression={this._digression} client={this._client} + conversationId={this._conversationId} triggeredSteps={this._triggeredSteps} highlightElements={this._highlightElements} />, diff --git a/packages/journey-manager/src/ui/index.tsx b/packages/journey-manager/src/ui/index.tsx index 62a9c116..fe7bb68f 100644 --- a/packages/journey-manager/src/ui/index.tsx +++ b/packages/journey-manager/src/ui/index.tsx @@ -20,6 +20,7 @@ export interface Ui { } const create = ( + conversationId: string, config: UiConfig | undefined, client: Client, findActiveTriggers: ( @@ -40,6 +41,7 @@ const create = ( uiElement.style.zIndex = "1000"; uiElement.config = config; uiElement.client = client; + uiElement.conversationId = conversationId; document.body.appendChild(uiElement); const updateHighlights = config.highlights