Skip to content

Commit 43be7fc

Browse files
committed
feature gate
1 parent 4c82373 commit 43be7fc

File tree

3 files changed

+328
-3
lines changed

3 files changed

+328
-3
lines changed

apps/dashboard/types/invitations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export type InvitationPageStatus =
99
| "expired"
1010
| "already-accepted";
1111

12-
export interface InvitationData {
12+
export type InvitationData = {
1313
organizationName: string;
1414
organizationSlug: string;
1515
inviterEmail: string;

apps/dashboard/types/performance.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export interface PerformanceEntry {
1+
export type PerformanceEntry = {
22
name: string;
33
visitors: number;
44
// Load time metrics
@@ -30,7 +30,7 @@ export interface PerformanceEntry {
3030
_uniqueKey?: string;
3131
}
3232

33-
export interface PerformanceSummary {
33+
export type PerformanceSummary = {
3434
avgLoadTime: number;
3535
fastPages: number;
3636
slowPages: number;
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
import { PLAN_IDS, type PlanId } from "./features.js";
2+
3+
/**
4+
* Pulse plan tiers - ordered from lowest to highest
5+
*/
6+
export const PULSE_PLAN_IDS = {
7+
FREE: "pulse_free",
8+
PRO: "pulse_pro",
9+
BUSINESS: "pulse_business",
10+
} as const;
11+
12+
export type PulsePlanId =
13+
(typeof PULSE_PLAN_IDS)[keyof typeof PULSE_PLAN_IDS];
14+
15+
/** Plan tier hierarchy (index = tier level, higher = more features) */
16+
export const PULSE_PLAN_HIERARCHY: PulsePlanId[] = [
17+
PULSE_PLAN_IDS.FREE,
18+
PULSE_PLAN_IDS.PRO,
19+
PULSE_PLAN_IDS.BUSINESS,
20+
];
21+
22+
/** Gated features - locked behind specific plans */
23+
export const PULSE_GATED_FEATURES = {
24+
// Basic checks
25+
BASIC_UPTIME_CHECKS: "basic_uptime_checks",
26+
EMAIL_ALERTS: "email_alerts",
27+
WEBHOOKS: "webhooks",
28+
PUBLIC_STATUS_PAGE: "public_status_page",
29+
DASHBOARD_INTEGRATION: "dashboard_integration",
30+
// Advanced checks
31+
SSL_CERTIFICATE_CHECKS: "ssl_certificate_checks",
32+
KEYWORD_CONTENT_MATCH: "keyword_content_match",
33+
MULTI_LOCATION_CHECKS: "multi_location_checks",
34+
ONE_MINUTE_FREQUENCY: "one_minute_frequency",
35+
// Enterprise features
36+
THIRTY_SECOND_FREQUENCY: "thirty_second_frequency",
37+
SYNTHETIC_TRANSACTIONS: "synthetic_transactions",
38+
ALERT_ESCALATION: "alert_escalation",
39+
SMS_VOICE_ALERTS: "sms_voice_alerts",
40+
HEARTBEAT_MONITORING: "heartbeat_monitoring",
41+
N_OUT_OF_M_FALSE_POSITIVE_REDUCTION: "n_out_of_m_false_positive_reduction",
42+
} as const;
43+
44+
export type PulseGatedFeatureId =
45+
(typeof PULSE_GATED_FEATURES)[keyof typeof PULSE_GATED_FEATURES];
46+
47+
/**
48+
* Plan feature matrix - edit this to control which features are enabled per plan
49+
*/
50+
export const PULSE_PLAN_FEATURES: Record<
51+
PulsePlanId,
52+
Record<PulseGatedFeatureId, boolean>
53+
> = {
54+
[PULSE_PLAN_IDS.FREE]: {
55+
[PULSE_GATED_FEATURES.BASIC_UPTIME_CHECKS]: true,
56+
[PULSE_GATED_FEATURES.EMAIL_ALERTS]: true,
57+
[PULSE_GATED_FEATURES.WEBHOOKS]: true,
58+
[PULSE_GATED_FEATURES.PUBLIC_STATUS_PAGE]: true,
59+
[PULSE_GATED_FEATURES.DASHBOARD_INTEGRATION]: true,
60+
[PULSE_GATED_FEATURES.SSL_CERTIFICATE_CHECKS]: false,
61+
[PULSE_GATED_FEATURES.KEYWORD_CONTENT_MATCH]: false,
62+
[PULSE_GATED_FEATURES.MULTI_LOCATION_CHECKS]: false,
63+
[PULSE_GATED_FEATURES.ONE_MINUTE_FREQUENCY]: false,
64+
[PULSE_GATED_FEATURES.THIRTY_SECOND_FREQUENCY]: false,
65+
[PULSE_GATED_FEATURES.SYNTHETIC_TRANSACTIONS]: false,
66+
[PULSE_GATED_FEATURES.ALERT_ESCALATION]: false,
67+
[PULSE_GATED_FEATURES.SMS_VOICE_ALERTS]: false,
68+
[PULSE_GATED_FEATURES.HEARTBEAT_MONITORING]: false,
69+
[PULSE_GATED_FEATURES.N_OUT_OF_M_FALSE_POSITIVE_REDUCTION]: false,
70+
},
71+
[PULSE_PLAN_IDS.PRO]: {
72+
[PULSE_GATED_FEATURES.BASIC_UPTIME_CHECKS]: true,
73+
[PULSE_GATED_FEATURES.EMAIL_ALERTS]: true,
74+
[PULSE_GATED_FEATURES.WEBHOOKS]: true,
75+
[PULSE_GATED_FEATURES.PUBLIC_STATUS_PAGE]: true,
76+
[PULSE_GATED_FEATURES.DASHBOARD_INTEGRATION]: true,
77+
[PULSE_GATED_FEATURES.SSL_CERTIFICATE_CHECKS]: true,
78+
[PULSE_GATED_FEATURES.KEYWORD_CONTENT_MATCH]: true,
79+
[PULSE_GATED_FEATURES.MULTI_LOCATION_CHECKS]: true,
80+
[PULSE_GATED_FEATURES.ONE_MINUTE_FREQUENCY]: true,
81+
[PULSE_GATED_FEATURES.THIRTY_SECOND_FREQUENCY]: false,
82+
[PULSE_GATED_FEATURES.SYNTHETIC_TRANSACTIONS]: false,
83+
[PULSE_GATED_FEATURES.ALERT_ESCALATION]: false,
84+
[PULSE_GATED_FEATURES.SMS_VOICE_ALERTS]: false,
85+
[PULSE_GATED_FEATURES.HEARTBEAT_MONITORING]: false,
86+
[PULSE_GATED_FEATURES.N_OUT_OF_M_FALSE_POSITIVE_REDUCTION]: false,
87+
},
88+
[PULSE_PLAN_IDS.BUSINESS]: {
89+
[PULSE_GATED_FEATURES.BASIC_UPTIME_CHECKS]: true,
90+
[PULSE_GATED_FEATURES.EMAIL_ALERTS]: true,
91+
[PULSE_GATED_FEATURES.WEBHOOKS]: true,
92+
[PULSE_GATED_FEATURES.PUBLIC_STATUS_PAGE]: true,
93+
[PULSE_GATED_FEATURES.DASHBOARD_INTEGRATION]: true,
94+
[PULSE_GATED_FEATURES.SSL_CERTIFICATE_CHECKS]: true,
95+
[PULSE_GATED_FEATURES.KEYWORD_CONTENT_MATCH]: true,
96+
[PULSE_GATED_FEATURES.MULTI_LOCATION_CHECKS]: true,
97+
[PULSE_GATED_FEATURES.ONE_MINUTE_FREQUENCY]: true,
98+
[PULSE_GATED_FEATURES.THIRTY_SECOND_FREQUENCY]: true,
99+
[PULSE_GATED_FEATURES.SYNTHETIC_TRANSACTIONS]: true,
100+
[PULSE_GATED_FEATURES.ALERT_ESCALATION]: true,
101+
[PULSE_GATED_FEATURES.SMS_VOICE_ALERTS]: true,
102+
[PULSE_GATED_FEATURES.HEARTBEAT_MONITORING]: true,
103+
[PULSE_GATED_FEATURES.N_OUT_OF_M_FALSE_POSITIVE_REDUCTION]: true,
104+
},
105+
};
106+
107+
/** Plan limits and configuration */
108+
export type PulsePlanLimits = {
109+
/** Number of monitors included */
110+
includedMonitors: number;
111+
/** Check frequency in minutes (or seconds for Business) */
112+
checkFrequencyMinutes?: number;
113+
checkFrequencySeconds?: number;
114+
/** Data retention period */
115+
dataRetentionDays?: number;
116+
dataRetentionMonths?: number;
117+
/** Number of check locations for multi-location checks */
118+
checkLocations?: number;
119+
};
120+
121+
/** Plan metadata including pricing and target audience */
122+
export type PulsePlanMetadata = {
123+
name: string;
124+
priceUsdMonthly: number;
125+
targetUser: string;
126+
limits: PulsePlanLimits;
127+
};
128+
129+
/**
130+
* Plan metadata - pricing, limits, and target audience
131+
*/
132+
export const PULSE_PLAN_METADATA: Record<PulsePlanId, PulsePlanMetadata> = {
133+
[PULSE_PLAN_IDS.FREE]: {
134+
name: "Pulse Free",
135+
priceUsdMonthly: 0,
136+
targetUser: "Hobbyists, Personal Projects",
137+
limits: {
138+
includedMonitors: 5,
139+
checkFrequencyMinutes: 5,
140+
dataRetentionDays: 30,
141+
},
142+
},
143+
[PULSE_PLAN_IDS.PRO]: {
144+
name: "Pulse Pro",
145+
priceUsdMonthly: 15,
146+
targetUser: "SMBs, Small Agencies",
147+
limits: {
148+
includedMonitors: 50,
149+
checkFrequencyMinutes: 1,
150+
dataRetentionMonths: 12,
151+
checkLocations: 3,
152+
},
153+
},
154+
[PULSE_PLAN_IDS.BUSINESS]: {
155+
name: "Pulse Business",
156+
priceUsdMonthly: 49,
157+
targetUser: "Growing SaaS, Dev Teams",
158+
limits: {
159+
includedMonitors: 200,
160+
checkFrequencySeconds: 30,
161+
dataRetentionMonths: 24,
162+
},
163+
},
164+
};
165+
166+
type PulseFeatureMeta = {
167+
name: string;
168+
description: string;
169+
upgradeMessage: string;
170+
minPlan?: PulsePlanId;
171+
};
172+
173+
export const PULSE_FEATURE_METADATA: Record<
174+
PulseGatedFeatureId,
175+
PulseFeatureMeta
176+
> = {
177+
[PULSE_GATED_FEATURES.BASIC_UPTIME_CHECKS]: {
178+
name: "Basic Uptime Checks",
179+
description: "HTTP/S, Ping, and Port monitoring",
180+
upgradeMessage: "Basic uptime checks are available on all plans",
181+
},
182+
[PULSE_GATED_FEATURES.EMAIL_ALERTS]: {
183+
name: "Email Alerts",
184+
description: "Receive email notifications when monitors go down",
185+
upgradeMessage: "Email alerts are available on all plans",
186+
},
187+
[PULSE_GATED_FEATURES.WEBHOOKS]: {
188+
name: "Webhooks",
189+
description: "Integrate with external services via webhooks",
190+
upgradeMessage: "Webhooks are available on all plans",
191+
},
192+
[PULSE_GATED_FEATURES.PUBLIC_STATUS_PAGE]: {
193+
name: "Public Status Page",
194+
description: "Share your service status publicly",
195+
upgradeMessage: "Public status pages are available on all plans",
196+
},
197+
[PULSE_GATED_FEATURES.DASHBOARD_INTEGRATION]: {
198+
name: "Dashboard Integration",
199+
description: "View monitor status in your dashboard",
200+
upgradeMessage: "Dashboard integration is available on all plans",
201+
},
202+
[PULSE_GATED_FEATURES.SSL_CERTIFICATE_CHECKS]: {
203+
name: "SSL Certificate Expiry Checks",
204+
description: "Monitor SSL certificate expiration dates",
205+
upgradeMessage: "Upgrade to Pro for SSL certificate checks",
206+
minPlan: PULSE_PLAN_IDS.PRO,
207+
},
208+
[PULSE_GATED_FEATURES.KEYWORD_CONTENT_MATCH]: {
209+
name: "Keyword/Content Match",
210+
description: "Verify specific content appears on monitored pages",
211+
upgradeMessage: "Upgrade to Pro for keyword/content matching",
212+
minPlan: PULSE_PLAN_IDS.PRO,
213+
},
214+
[PULSE_GATED_FEATURES.MULTI_LOCATION_CHECKS]: {
215+
name: "Multi-Location Checks",
216+
description: "Monitor from multiple geographic locations",
217+
upgradeMessage: "Upgrade to Pro for multi-location checks",
218+
minPlan: PULSE_PLAN_IDS.PRO,
219+
},
220+
[PULSE_GATED_FEATURES.ONE_MINUTE_FREQUENCY]: {
221+
name: "1-Minute Check Frequency",
222+
description: "Check your monitors every minute",
223+
upgradeMessage: "Upgrade to Pro for 1-minute check frequency",
224+
minPlan: PULSE_PLAN_IDS.PRO,
225+
},
226+
[PULSE_GATED_FEATURES.THIRTY_SECOND_FREQUENCY]: {
227+
name: "30-Second Check Frequency",
228+
description: "Check your monitors every 30 seconds",
229+
upgradeMessage: "Upgrade to Business for 30-second check frequency",
230+
minPlan: PULSE_PLAN_IDS.BUSINESS,
231+
},
232+
[PULSE_GATED_FEATURES.SYNTHETIC_TRANSACTIONS]: {
233+
name: "Synthetic Transactions",
234+
description: "Multi-step checks that simulate user workflows",
235+
upgradeMessage: "Upgrade to Business for synthetic transactions",
236+
minPlan: PULSE_PLAN_IDS.BUSINESS,
237+
},
238+
[PULSE_GATED_FEATURES.ALERT_ESCALATION]: {
239+
name: "Alert Escalation Policies",
240+
description: "Configure alert escalation rules",
241+
upgradeMessage: "Upgrade to Business for alert escalation",
242+
minPlan: PULSE_PLAN_IDS.BUSINESS,
243+
},
244+
[PULSE_GATED_FEATURES.SMS_VOICE_ALERTS]: {
245+
name: "SMS/Voice Call Alerts",
246+
description: "Receive alerts via SMS or voice calls via Twilio",
247+
upgradeMessage: "Upgrade to Business for SMS/voice alerts",
248+
minPlan: PULSE_PLAN_IDS.BUSINESS,
249+
},
250+
[PULSE_GATED_FEATURES.HEARTBEAT_MONITORING]: {
251+
name: "Heartbeat Monitoring",
252+
description: "Monitor applications that send heartbeat signals",
253+
upgradeMessage: "Upgrade to Business for heartbeat monitoring",
254+
minPlan: PULSE_PLAN_IDS.BUSINESS,
255+
},
256+
[PULSE_GATED_FEATURES.N_OUT_OF_M_FALSE_POSITIVE_REDUCTION]: {
257+
name: "N-out-of-M False Positive Reduction",
258+
description: "Reduce false positives by requiring N failures out of M checks",
259+
upgradeMessage:
260+
"Upgrade to Business for N-out-of-M false positive reduction",
261+
minPlan: PULSE_PLAN_IDS.BUSINESS,
262+
},
263+
};
264+
265+
/**
266+
* Map Pulse plan to equivalent regular plan for feature checks
267+
* Pulse Free maps to regular Free plan
268+
*/
269+
export function getRegularPlanForPulsePlan(
270+
pulsePlanId: PulsePlanId | string | null
271+
): PlanId {
272+
const pulsePlan = (pulsePlanId ?? PULSE_PLAN_IDS.FREE) as PulsePlanId;
273+
274+
if (pulsePlan === PULSE_PLAN_IDS.FREE) {
275+
return PLAN_IDS.FREE;
276+
}
277+
278+
// For other Pulse plans, return free as default
279+
// You can extend this mapping if needed
280+
return PLAN_IDS.FREE;
281+
}
282+
283+
/** Check if a plan has access to a gated feature */
284+
export function isPulsePlanFeatureEnabled(
285+
planId: PulsePlanId | string | null,
286+
feature: PulseGatedFeatureId
287+
): boolean {
288+
const plan = (planId ?? PULSE_PLAN_IDS.FREE) as PulsePlanId;
289+
return PULSE_PLAN_FEATURES[plan]?.[feature] ?? false;
290+
}
291+
292+
/** Get the minimum plan required for a feature */
293+
export function getMinimumPulsePlanForFeature(
294+
feature: PulseGatedFeatureId
295+
): PulsePlanId | null {
296+
for (const plan of PULSE_PLAN_HIERARCHY) {
297+
if (PULSE_PLAN_FEATURES[plan][feature]) {
298+
return plan;
299+
}
300+
}
301+
return null;
302+
}
303+
304+
/** Get plan metadata */
305+
export function getPulsePlanMetadata(
306+
planId: PulsePlanId | string | null
307+
): PulsePlanMetadata {
308+
const plan = (planId ?? PULSE_PLAN_IDS.FREE) as PulsePlanId;
309+
return PULSE_PLAN_METADATA[plan] ?? PULSE_PLAN_METADATA[PULSE_PLAN_IDS.FREE];
310+
}
311+
312+
/** Get plan limits */
313+
export function getPulsePlanLimits(
314+
planId: PulsePlanId | string | null
315+
): PulsePlanLimits {
316+
const metadata = getPulsePlanMetadata(planId);
317+
return metadata.limits;
318+
}
319+
320+
/** Product information */
321+
export const PULSE_PRODUCT_INFO = {
322+
productName: "Databuddy Pulse",
323+
coreValueProposition:
324+
"Integrated, Privacy-First Uptime Monitoring: Know when your site is down, and why, without compromising user privacy.",
325+
} as const;

0 commit comments

Comments
 (0)