Files
calendar/apps/web/modules/feature-opt-in/hooks/useUserFeatureOptIn.ts
T
Eunjae LeeGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
18049bc1d6 refactor: update i18n keys to match effectiveReason values (#27381)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-01-29 14:16:12 +00:00

115 lines
4.2 KiB
TypeScript

"use client";
import type { EffectiveStateReason } from "@calcom/features/feature-opt-in/lib/computeEffectiveState";
import type { NormalizedFeature, UseFeatureOptInResult } from "@calcom/features/feature-opt-in/types";
import type { FeatureState } from "@calcom/features/flags/config";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { showToast } from "@calcom/ui/components/toast";
import type { TFunction } from "i18next";
import { useCallback, useMemo } from "react";
type UserFeatureData = {
featureId: string;
globalEnabled: boolean;
userState: FeatureState | undefined;
effectiveReason: EffectiveStateReason;
};
function useMutationCallbacks(onSuccessCallback: () => void): { onSuccess: () => void; onError: () => void } {
const { t } = useLocale();
return useMemo(
() => ({
onSuccess: (): void => {
onSuccessCallback();
showToast(t("settings_updated_successfully"), "success");
},
onError: (): void => {
showToast(t("error_updating_settings"), "error");
},
}),
[onSuccessCallback, t]
);
}
function normalizeUserFeatures(data: UserFeatureData[] | undefined): NormalizedFeature[] {
return (data ?? []).map((feature) => ({
slug: feature.featureId,
globalEnabled: feature.globalEnabled,
currentState: feature.userState ?? "inherit",
effectiveReason: feature.effectiveReason,
}));
}
const WARNING_REASONS: Set<string> = new Set([
"feature_org_disabled",
"feature_all_teams_disabled",
"feature_any_team_disabled",
"feature_no_explicit_enablement",
"feature_user_only_not_allowed",
]);
function createUserBlockedWarningFn(t: TFunction): (feature: NormalizedFeature) => string | null {
return (feature: NormalizedFeature): string | null => {
if (!feature.effectiveReason || !WARNING_REASONS.has(feature.effectiveReason)) return null;
return t(feature.effectiveReason);
};
}
function isUserBlockedByHigherLevel(feature: NormalizedFeature): boolean {
return (
feature.effectiveReason === "feature_org_disabled" ||
feature.effectiveReason === "feature_all_teams_disabled" ||
feature.effectiveReason === "feature_any_team_disabled"
);
}
/**
* Hook for managing feature opt-in at the user (personal) level.
*/
export function useUserFeatureOptIn(): UseFeatureOptInResult {
const { t } = useLocale();
const utils = trpc.useUtils();
const featuresQuery = trpc.viewer.featureOptIn.listForUser.useQuery(undefined, {
refetchOnWindowFocus: false,
});
const autoOptInQuery = trpc.viewer.featureOptIn.getUserAutoOptIn.useQuery(undefined, {
refetchOnWindowFocus: false,
});
const invalidateFeatures = useCallback(() => utils.viewer.featureOptIn.listForUser.invalidate(), [utils]);
const invalidateFeaturesAndAutoOptIn = useCallback(() => {
utils.viewer.featureOptIn.getUserAutoOptIn.invalidate();
utils.viewer.featureOptIn.listForUser.invalidate();
}, [utils]);
const setStateMutationCallbacks = useMutationCallbacks(invalidateFeatures);
const setAutoOptInMutationCallbacks = useMutationCallbacks(invalidateFeaturesAndAutoOptIn);
const setStateMutation = trpc.viewer.featureOptIn.setUserState.useMutation(setStateMutationCallbacks);
const setAutoOptInMutation = trpc.viewer.featureOptIn.setUserAutoOptIn.useMutation(
setAutoOptInMutationCallbacks
);
const features = useMemo(() => normalizeUserFeatures(featuresQuery.data), [featuresQuery.data]);
const setFeatureState = (slug: string, state: FeatureState): void =>
setStateMutation.mutate({ slug, state });
const setAutoOptIn = (checked: boolean): void => setAutoOptInMutation.mutate({ autoOptIn: checked });
const getBlockedWarning = createUserBlockedWarningFn(t);
return {
features,
autoOptIn: autoOptInQuery.data?.autoOptIn ?? false,
isLoading: featuresQuery.isLoading || autoOptInQuery.isLoading,
setFeatureState,
setAutoOptIn,
isStateMutationPending: setStateMutation.isPending,
isAutoOptInMutationPending: setAutoOptInMutation.isPending,
toggleLabels: { enabled: t("feature_on"), disabled: t("feature_off"), inherit: t("use_default") },
autoOptInDescription: t("auto_opt_in_experimental_description_personal"),
getBlockedWarning,
isBlockedByHigherLevel: isUserBlockedByHigherLevel,
};
}