Lead Radar: исходный код
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
9749da5ae7
201 files changed
+97685
No files matched your search
@@ -0,0 +1,66 @@
|
||||
/** Тарифы (ТЗ, раздел 2). Лимиты и модули проверяются на сервере. */
|
||||
export const PLANS = ['solo', 'team', 'agency'] as const;
|
||||
export type Plan = (typeof PLANS)[number];
|
||||
|
||||
export const BILLING_MODELS = ['fixed', 'percent'] as const;
|
||||
export type BillingModel = (typeof BILLING_MODELS)[number];
|
||||
|
||||
export const MODULES = ['web', 'analytics', 'archive'] as const;
|
||||
/** web — веб-кабинет; analytics — аналитика по сотрудникам; archive — база бывших клиентов и объектов. */
|
||||
export type Module = (typeof MODULES)[number];
|
||||
|
||||
export interface PlanSpec {
|
||||
maxMembers: number;
|
||||
setupFeeUsd: number;
|
||||
monthlyUsd: number;
|
||||
/** Процент от комиссии по сделкам; null — модель «%» недоступна. */
|
||||
commissionPercent: number | null;
|
||||
modules: readonly Module[];
|
||||
}
|
||||
|
||||
export const PLAN_SPECS: Record<Plan, PlanSpec> = {
|
||||
solo: { maxMembers: 1, setupFeeUsd: 0, monthlyUsd: 40, commissionPercent: null, modules: [] },
|
||||
team: {
|
||||
maxMembers: 10,
|
||||
setupFeeUsd: 500,
|
||||
monthlyUsd: 350,
|
||||
commissionPercent: 10,
|
||||
modules: ['web', 'analytics', 'archive'],
|
||||
},
|
||||
agency: {
|
||||
maxMembers: 35,
|
||||
setupFeeUsd: 750,
|
||||
monthlyUsd: 550,
|
||||
commissionPercent: 7,
|
||||
modules: ['web', 'analytics', 'archive'],
|
||||
},
|
||||
};
|
||||
|
||||
export const hasModule = (plan: Plan, module: Module) => PLAN_SPECS[plan].modules.includes(module);
|
||||
|
||||
/** Solo всегда на фиксе; у остальных модель выбирается. */
|
||||
export const allowedBillingModels = (plan: Plan): readonly BillingModel[] =>
|
||||
PLAN_SPECS[plan].commissionPercent === null ? ['fixed'] : BILLING_MODELS;
|
||||
|
||||
/** Срок отсрочки по умолчанию: столько дней после окончания подписки сервис ещё работает. */
|
||||
export const DEFAULT_GRACE_DAYS = 15;
|
||||
|
||||
export const ACCESS_STATES = ['active', 'overdue', 'restricted', 'disabled'] as const;
|
||||
/**
|
||||
* active — подписка действует; overdue — просрочена, сервис работает;
|
||||
* restricted — отсрочка истекла: без уведомлений о лидах, CRM только на чтение;
|
||||
* disabled — доступ выключен суперадмином либо компания ещё не оплачена.
|
||||
*/
|
||||
export type AccessState = (typeof ACCESS_STATES)[number];
|
||||
|
||||
export function accessState(
|
||||
company: { accessEnabled: boolean; subscriptionEndsAt: Date | null; graceDays: number },
|
||||
now = new Date(),
|
||||
): AccessState {
|
||||
if (!company.accessEnabled || !company.subscriptionEndsAt) return 'disabled';
|
||||
const end = company.subscriptionEndsAt.getTime();
|
||||
if (now.getTime() <= end) return 'active';
|
||||
return now.getTime() <= end + company.graceDays * 86_400_000 ? 'overdue' : 'restricted';
|
||||
}
|
||||
|
||||
export const canWrite = (state: AccessState) => state === 'active' || state === 'overdue';
|
||||
Reference in new issue
Block a user