Files
calendar/packages/features/feature-opt-in/services/IFeatureOptInService.ts
T
Eunjae LeeGitHubeunjae@cal.com <hey@eunjae.dev>Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
ddceabe5d4 fix: use FeatureOptInService for bookings-v3 check (#27183)
* fix: use FeatureOptInService for bookings-v3 check

* refactor: simplify FeatureOptInService API and fix bookings page

- Add resolveFeatureStates method that takes only userId and featureIds
- Update listFeaturesForUser to take only userId parameter
- Remove getUserOrgAndTeamIds from _router.ts (now internal to service)
- Fix bookings page to use new resolveFeatureStates method
- Parallelize getUserFeaturesStatus and resolveFeatureStates calls

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* check session?.user?.id first

* remove comment

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-01-28 14:28:36 +00:00

100 lines
2.7 KiB
TypeScript

import type { FeatureId, FeatureState } from "@calcom/features/flags/config";
import type { EffectiveStateReason } from "../lib/computeEffectiveState";
import type { OptInFeatureScope } from "../types";
export type { EffectiveStateReason };
export type UserRoleContext = {
isOrgAdmin: boolean;
orgId: number | null;
adminTeamIds: number[];
adminTeamNames: { id: number; name: string }[];
};
export type FeatureOptInEligibilityResult =
| {
status: "invalid_feature";
canOptIn: false;
userRoleContext: null;
blockingReason: null;
}
| {
status: "feature_disabled";
canOptIn: false;
userRoleContext: null;
blockingReason: string;
}
| {
status: "already_enabled";
canOptIn: false;
userRoleContext: null;
blockingReason: null;
}
| {
status: "blocked";
canOptIn: false;
userRoleContext: UserRoleContext;
blockingReason: string;
}
| {
status: "can_opt_in";
canOptIn: true;
userRoleContext: UserRoleContext;
blockingReason: null;
};
export type ResolvedFeatureState = {
featureId: FeatureId;
globalEnabled: boolean;
orgState: FeatureState;
teamStates: FeatureState[];
userState: FeatureState | undefined;
effectiveEnabled: boolean;
effectiveReason: EffectiveStateReason;
orgAutoOptIn: boolean;
teamAutoOptIns: boolean[];
userAutoOptIn: boolean;
};
export interface IFeatureOptInService {
resolveFeatureStatesAcrossTeams(input: {
userId: number;
orgId: number | null;
teamIds: number[];
featureIds: FeatureId[];
}): Promise<Record<string, ResolvedFeatureState>>;
resolveFeatureStates(input: {
userId: number;
featureIds: FeatureId[];
}): Promise<Record<string, ResolvedFeatureState>>;
listFeaturesForUser(input: { userId: number }): Promise<ResolvedFeatureState[]>;
listFeaturesForTeam(input: {
teamId: number;
parentOrgId?: number | null;
scope?: OptInFeatureScope;
}): Promise<
{ featureId: FeatureId; globalEnabled: boolean; teamState: FeatureState; orgState: FeatureState }[]
>;
setUserFeatureState(
input:
| { userId: number; featureId: FeatureId; state: "enabled" | "disabled"; assignedBy: number }
| { userId: number; featureId: FeatureId; state: "inherit" }
): Promise<void>;
setTeamFeatureState(
input:
| {
teamId: number;
featureId: FeatureId;
state: "enabled" | "disabled";
assignedBy: number;
scope?: OptInFeatureScope;
}
| { teamId: number; featureId: FeatureId; state: "inherit"; scope?: OptInFeatureScope }
): Promise<void>;
checkFeatureOptInEligibility(input: {
userId: number;
featureId: string;
}): Promise<FeatureOptInEligibilityResult>;
}