From f68b761e6861fc79595be3757410e45afa939777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Vidra?= Date: Fri, 18 Oct 2024 13:55:03 +0200 Subject: [PATCH] feat: add onFlowUpdate option (#209) --- package.json | 2 +- workspaces/js/src/core/flow-state.ts | 22 +++++++++++++++++ workspaces/js/src/core/flows-context.ts | 3 +++ workspaces/js/src/types/options.ts | 32 +++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 22425aa..08f7c7a 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,5 @@ "husky": "^9.1.6", "prettier": "^3.3.3" }, - "packageManager": "pnpm@9.6.0" + "packageManager": "pnpm@9.12.2" } diff --git a/workspaces/js/src/core/flow-state.ts b/workspaces/js/src/core/flow-state.ts index f8c1653..bf3cbe7 100644 --- a/workspaces/js/src/core/flow-state.ts +++ b/workspaces/js/src/core/flow-state.ts @@ -2,6 +2,7 @@ import type { DebugEvent, Flow, FlowStep, FlowStepIndex, TrackingEvent } from ". import { hash } from "../lib/hash"; import { isModalStep, isTooltipStep } from "../lib/step-type"; import { log } from "../lib/log"; +import { getPathname } from "../lib/location"; import { render } from "./render"; import type { FlowsContext } from "./flows-context"; @@ -42,6 +43,7 @@ export class FlowState { (i) => i.flowId === flowId, ); if (!flowAlreadyRunning) void this.track({ type: "startFlow" }); + this.onFlowUpdate(); this.enterStep(); } @@ -62,9 +64,25 @@ export class FlowState { stepIndex: this.step, stepHash: this.currentStep ? await hash(JSON.stringify(this.currentStep)) : undefined, flowHash: await hash(JSON.stringify(this.flow)), + stepId: this.currentStep?.stepId, ...props, }); } + onFlowUpdate({ end }: { end?: boolean } = {}): void { + const prevStepIndex = this.stepHistory.at(-2); + const prevStep = + this.flow && prevStepIndex !== undefined + ? getStep({ flow: this.flow, step: prevStepIndex }) + : undefined; + const currentStep = this.currentStep; + this.flowsContext.onFlowUpdate?.({ + flowId: this.flowId, + location: getPathname(), + currentStep: end ? undefined : currentStep, + prevStep: end ? currentStep : prevStep, + }); + } + async debug( props: Pick, ): Promise<{ referenceId: string } | undefined> { @@ -134,6 +152,7 @@ export class FlowState { if (this.currentStep) this.flowsContext.onNextStep?.(this.currentStep); void this.track({ type: "nextStep" }); + this.onFlowUpdate(); return this; } get hasNextStep(): boolean { @@ -166,6 +185,7 @@ export class FlowState { this.stepHistory = this.stepHistory.slice(0, -1); if (this.currentStep) this.flowsContext.onPrevStep?.(this.currentStep); void this.track({ type: "prevStep" }); + this.onFlowUpdate(); return this; } get hasPrevStep(): boolean { @@ -206,6 +226,7 @@ export class FlowState { cancel(): this { void this.track({ type: "cancelFlow" }); + this.onFlowUpdate({ end: true }); this.flowsContext.flowSeen(this.flowId); this.unmount(); return this; @@ -213,6 +234,7 @@ export class FlowState { finish(): this { void this.track({ type: "finishFlow" }); + this.onFlowUpdate({ end: true }); this.flowsContext.flowSeen(this.flowId); this.unmount(); return this; diff --git a/workspaces/js/src/core/flows-context.ts b/workspaces/js/src/core/flows-context.ts index 5e201cc..aea6c38 100644 --- a/workspaces/js/src/core/flows-context.ts +++ b/workspaces/js/src/core/flows-context.ts @@ -12,6 +12,7 @@ import type { DebugEvent, IdentifyUserOptions, SeenFlow, + OnFlowUpdate, } from "../types"; import { log } from "../lib/log"; import { getPersistentState, setPersistentState } from "../lib/persistent-state"; @@ -95,6 +96,7 @@ export class FlowsContext { flowsById?: Record; onNextStep?: (step: FlowStep) => void; onPrevStep?: (step: FlowStep) => void; + onFlowUpdate?: OnFlowUpdate; tracking?: Tracking; debug?: Debug; onSeenFlowsChange?: (seenFlows: SeenFlow[]) => void; @@ -107,6 +109,7 @@ export class FlowsContext { this.projectId = options.projectId; this.onNextStep = options.onNextStep; this.onPrevStep = options.onPrevStep; + this.onFlowUpdate = options.onFlowUpdate; this.tracking = options.tracking; this.debug = options._debug; this.seenFlows = options.seenFlows ? [...options.seenFlows] : this.seenFlows; diff --git a/workspaces/js/src/types/options.ts b/workspaces/js/src/types/options.ts index 5e7c922..80b5e0e 100644 --- a/workspaces/js/src/types/options.ts +++ b/workspaces/js/src/types/options.ts @@ -25,9 +25,37 @@ export interface TrackingEvent { * - "/search?query=foo" - Query params are included */ location: string; + /** + * Current step ID. + */ + stepId?: string; } export type Tracking = (event: TrackingEvent) => void; +export interface OnFlowUpdateProps { + /** + * Previous step that user was on. Undefined if the flow has just started. + */ + prevStep?: FlowStep; + /** + * The current step that user is on. Undefined if the flow has ended. + */ + currentStep?: FlowStep; + /** + * The flow ID that the step belongs to. + */ + flowId: string; + /** + * Browser location + * @example + * - "/" - Root + * - "/checkout" + * - "/search?query=foo" - Query params are included + */ + location: string; +} +export type OnFlowUpdate = (props: OnFlowUpdateProps) => void; + export interface DebugEvent extends Omit { type: "tooltipError" | "invalidateTooltipError" | "invalidStepError"; /** @@ -81,6 +109,10 @@ export interface FlowsOptions extends IdentifyUserOptions { * Method to be called when the user goes back to a previous step. */ onPrevStep?: (step: FlowStep) => void; + /** + * Method to be called when a flow is started, ended or its step changes. + */ + onFlowUpdate?: OnFlowUpdate; /** * Method for integrating 3rd party tracking tools. When using Flows Cloud, this is automatically managed. */