Property Management Network — Next.js 16 (App Router), Better Auth, Drizzle ORM over PostgreSQL, Stripe, OpenAI, Resend. Includes: - Security hardening: access-control/IDOR fixes, TLS-by-default DB layer, constant-time cron auth, strict security headers, atomic AI quota gating, HTML/email output encoding, demo-backdoor disabled in production. - Superadmin dashboard at /admin (overview/MRR, server-paginated users with ban/impersonate/plan/delete, billing, platform activity + admin audit log, AI usage, system health) via the Better Auth admin plugin. - Seed/migration utility scripts under scripts/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
1.7 KiB
TypeScript
78 lines
1.7 KiB
TypeScript
import type { Plan, PlanLimits } from "@/types"
|
|
|
|
export const PLAN_LIMITS: Record<Plan, PlanLimits> = {
|
|
starter: {
|
|
maxProperties: 1,
|
|
maxTenants: 3,
|
|
maxAiCalls: 0,
|
|
maxStorageMB: 100,
|
|
hasTeamAccess: false,
|
|
hasWhiteLabel: false,
|
|
},
|
|
pro: {
|
|
maxProperties: 10,
|
|
maxTenants: Infinity,
|
|
maxAiCalls: 50,
|
|
maxStorageMB: 5120,
|
|
hasTeamAccess: false,
|
|
hasWhiteLabel: false,
|
|
},
|
|
landlord: {
|
|
maxProperties: Infinity,
|
|
maxTenants: Infinity,
|
|
maxAiCalls: 200,
|
|
maxStorageMB: 25600,
|
|
hasTeamAccess: true,
|
|
hasWhiteLabel: true,
|
|
},
|
|
lifetime: {
|
|
maxProperties: Infinity,
|
|
maxTenants: Infinity,
|
|
maxAiCalls: 200,
|
|
maxStorageMB: 25600,
|
|
hasTeamAccess: true,
|
|
hasWhiteLabel: true,
|
|
},
|
|
}
|
|
|
|
export const PLAN_PRICES: Record<string, { plan: Plan; priceId: string; amount: number; interval: string }> = {
|
|
pro: {
|
|
plan: "pro",
|
|
priceId: process.env.STRIPE_PRO_MONTHLY_PRICE_ID!,
|
|
amount: 29,
|
|
interval: "month",
|
|
},
|
|
landlord: {
|
|
plan: "landlord",
|
|
priceId: process.env.STRIPE_LANDLORD_MONTHLY_PRICE_ID!,
|
|
amount: 59,
|
|
interval: "month",
|
|
},
|
|
lifetime: {
|
|
plan: "lifetime",
|
|
priceId: process.env.STRIPE_LIFETIME_PRICE_ID!,
|
|
amount: 199,
|
|
interval: "one_time",
|
|
},
|
|
}
|
|
|
|
export function checkLimit(
|
|
plan: Plan,
|
|
resource: keyof PlanLimits,
|
|
currentCount: number
|
|
): boolean {
|
|
const limit = PLAN_LIMITS[plan][resource]
|
|
if (typeof limit === "boolean") return limit
|
|
return currentCount < (limit as number)
|
|
}
|
|
|
|
export function getPlanLabel(plan: Plan): string {
|
|
const labels: Record<Plan, string> = {
|
|
starter: "Starter",
|
|
pro: "Pro",
|
|
landlord: "Landlord",
|
|
lifetime: "Lifetime",
|
|
}
|
|
return labels[plan]
|
|
}
|