c3a8301b0c
* fix: hide feature opt-in banner and feedback forms during impersonation Co-Authored-By: sean@cal.com <Sean@brydon.io> * fix: keep opt-in banner visible during impersonation, only hide feedback forms Co-Authored-By: sean@cal.com <Sean@brydon.io> * fix: allow feedback form during impersonation in development mode Co-Authored-By: sean@cal.com <Sean@brydon.io> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
103 lines
3.6 KiB
TypeScript
103 lines
3.6 KiB
TypeScript
"use client";
|
|
|
|
import type { OptInFeatureConfig } from "@calcom/features/feature-opt-in/config";
|
|
import { isENVDev } from "@calcom/lib/env";
|
|
import { useSession } from "next-auth/react";
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import {
|
|
getFeatureOptInTimestamp,
|
|
isFeatureFeedbackShown,
|
|
setFeatureFeedbackShown,
|
|
} from "../lib/feature-opt-in-storage";
|
|
|
|
/** Delay before showing the feedback dialog to let the page finish loading */
|
|
const PAGE_LOAD_DELAY_MS = 5000;
|
|
|
|
export interface OptInFeedbackState {
|
|
/** Whether the feedback dialog should be shown */
|
|
showFeedbackDialog: boolean;
|
|
/** Props to pass to the FeedbackDialog component */
|
|
feedbackDialogProps: {
|
|
surveyId: string;
|
|
ratingQuestionId: string;
|
|
commentQuestionId: string;
|
|
titleKey?: string;
|
|
descriptionKey?: string;
|
|
showOn?: "all" | "desktop" | "mobile";
|
|
} | null;
|
|
/** Call this when the dialog is closed (either submitted or skipped) */
|
|
onFeedbackComplete: () => void;
|
|
}
|
|
|
|
/**
|
|
* Hook to manage feedback dialog display after a user opts into a feature.
|
|
* Shows a custom feedback dialog after a configurable delay (waitAfterDays).
|
|
*/
|
|
function useOptInFeedback(featureId: string, featureConfig: OptInFeatureConfig | null): OptInFeedbackState {
|
|
const { data: session } = useSession();
|
|
const isImpersonating = !!session?.user?.impersonatedBy;
|
|
const [showFeedbackDialog, setShowFeedbackDialog] = useState(false);
|
|
const hasTriggeredRef = useRef(false);
|
|
|
|
const onFeedbackComplete = useCallback(() => {
|
|
setShowFeedbackDialog(false);
|
|
setFeatureFeedbackShown(featureId);
|
|
}, [featureId]);
|
|
|
|
useEffect(() => {
|
|
if (isImpersonating && !isENVDev) return;
|
|
if (!featureConfig?.formbricks) return;
|
|
|
|
// Don't trigger if already triggered this session
|
|
if (hasTriggeredRef.current) return;
|
|
|
|
// Don't show if feedback was already shown (persisted in localStorage)
|
|
const alreadyShown = isFeatureFeedbackShown(featureId);
|
|
if (alreadyShown) return;
|
|
|
|
// Don't trigger if user hasn't opted in yet
|
|
const optInTimestamp = getFeatureOptInTimestamp(featureId);
|
|
if (!optInTimestamp) return;
|
|
|
|
// Don't trigger if survey is not configured
|
|
const { surveyId, questions } = featureConfig.formbricks;
|
|
if (!surveyId || !questions?.ratingQuestionId || !questions?.commentQuestionId) return;
|
|
|
|
const { waitAfterDays } = featureConfig.formbricks;
|
|
const waitAfterMs = waitAfterDays * 24 * 60 * 60 * 1000;
|
|
const timeSinceOptIn = Date.now() - optInTimestamp;
|
|
|
|
// Don't show feedback if not enough time has passed since opt-in
|
|
// (e.g., wait 3 days after opt-in before asking for feedback)
|
|
if (timeSinceOptIn < waitAfterMs) return;
|
|
|
|
const triggerFeedback = (): void => {
|
|
hasTriggeredRef.current = true;
|
|
setShowFeedbackDialog(true);
|
|
};
|
|
|
|
// Show after page load delay to let the page finish loading
|
|
const timer = setTimeout(triggerFeedback, PAGE_LOAD_DELAY_MS);
|
|
return () => clearTimeout(timer);
|
|
}, [featureId, featureConfig, isImpersonating]);
|
|
|
|
const feedbackDialogProps = featureConfig?.formbricks?.surveyId
|
|
? {
|
|
surveyId: featureConfig.formbricks.surveyId,
|
|
ratingQuestionId: featureConfig.formbricks.questions.ratingQuestionId,
|
|
commentQuestionId: featureConfig.formbricks.questions.commentQuestionId,
|
|
titleKey: featureConfig.formbricks.titleKey,
|
|
descriptionKey: featureConfig.formbricks.descriptionKey,
|
|
showOn: featureConfig.formbricks.showOn,
|
|
}
|
|
: null;
|
|
|
|
return {
|
|
showFeedbackDialog,
|
|
feedbackDialogProps,
|
|
onFeedbackComplete,
|
|
};
|
|
}
|
|
|
|
export { useOptInFeedback };
|