Files
dataroom/lib/swr/use-billing.ts
zeekay caad90bc04 rip: enterprise dirs + paywalls — full AGPL, no commercial-license code
Drop the dual-licensed `ee/` tree entirely. All features previously
locked behind the Hanzo Dataroom Commercial License are now part of the
AGPL-3.0 core and freely available — custom domains, branded viewing,
advanced analytics, watermarks, access controls, Q&A, AI vector stores,
workflows, SAML SSO, dataroom invitations, conversations, templates.

Layout changes:
* `ee/features/{ai,conversations,workflows,permissions,storage,templates,
  dataroom-invitations,access-notifications,security,conversions}/` →
  `features/<name>/` (AGPL).
* `ee/limits/` → `lib/billing/limits/` with every plan resolving to an
  unlimited profile (no users/links/documents/domains/datarooms cap,
  conversations and watermarks always on).
* `ee/stripe/` → `lib/billing/legacy/` (already a compat shim over
  `@hanzoai/commerce`; renewal-reminder webhook is now a no-op).
* `ee/features/billing/cancellation/` deleted. Pause/unpause/cancel/
  retention routes now compile against `lib/billing/cancellation.ts`
  stubs that return 410 Gone; `isTeamPaused`/`isTeamPausedById` in
  `lib/billing/paused.ts` always report active. `CancellationModal` is
  a no-op shim in `components/billing/`.
* `app/(ee)/api/*` route group folded into `app/api/*` (SAML auth,
  SCIM, AI chat, workflows, FAQ, link-upload).

Build: `npm run build` green.
2026-06-07 11:46:48 -07:00

147 lines
3.9 KiB
TypeScript

import { useMemo } from "react";
import { useTeam } from "@/context/team-context";
import { PLAN_NAME_MAP } from "@/lib/billing/legacy/constants";
import { SubscriptionDiscount } from "@/lib/billing/legacy/functions/get-subscription-item";
import useSWR from "swr";
import { fetcher } from "@/lib/utils";
interface BillingProps {
id: string;
plan: string;
startsAt: Date | null;
endsAt: Date | null;
subscriptionId: string | null;
_count: {
documents: number;
};
}
export function useBilling() {
const teamInfo = useTeam();
const { data, error } = useSWR<BillingProps>(
teamInfo?.currentTeam && `/api/teams/${teamInfo.currentTeam.id}/billing`,
fetcher,
{
dedupingInterval: 30000,
},
);
return {
...data,
error,
loading: !data && !error,
};
}
export type BasePlan =
| "free"
| "starter"
| "pro"
| "trial"
| "business"
| "datarooms"
| "datarooms-plus"
| "datarooms-premium";
type PlanWithTrial = `${BasePlan}+drtrial`;
type PlanWithOld = `${BasePlan}+old` | `${BasePlan}+drtrial+old`;
type PlanResponse = {
plan: BasePlan | PlanWithTrial | PlanWithOld;
startsAt: Date | null;
endsAt: Date | null;
pausedAt: Date | null;
pauseStartsAt: Date | null;
pauseEndsAt: Date | null;
isPaused: boolean;
cancelledAt: Date | null;
isCustomer: boolean;
subscriptionCycle: "monthly" | "yearly";
discount: SubscriptionDiscount | null;
};
interface PlanDetails {
plan: BasePlan | null;
trial: string | null;
old: boolean;
}
function parsePlan(plan: BasePlan | PlanWithTrial | PlanWithOld): PlanDetails {
if (!plan || typeof plan !== "string") {
return { plan: null, trial: null, old: false };
}
try {
// Split the plan on '+'
const parts = plan.split("+");
return {
plan: parts[0] as BasePlan, // Always the base plan
trial: parts.includes("drtrial") ? "drtrial" : null, // 'drtrial' if present, otherwise null
old: parts.includes("old"), // true if 'old' is present, otherwise false
};
} catch (error) {
console.error("Error parsing plan:", error);
return { plan: null, trial: null, old: false };
}
}
export function usePlan({
withDiscount = false,
}: { withDiscount?: boolean } = {}) {
const teamInfo = useTeam();
const teamId = teamInfo?.currentTeam?.id;
const {
data: plan,
error,
mutate,
} = useSWR<PlanResponse>(
teamId
? `/api/teams/${teamId}/billing/plan${withDiscount ? "?withDiscount=true" : ""}`
: null,
fetcher,
);
// Parse the plan using the parsing function
const parsedPlan = useMemo(() => {
if (!plan || !plan.plan) {
return { plan: null, trial: null, old: false };
}
return parsePlan(plan.plan);
}, [plan]);
return {
plan: parsedPlan.plan ?? "free",
planName: PLAN_NAME_MAP[parsedPlan.plan ?? "free"],
originalPlan: parsedPlan.plan + (parsedPlan.old ? "+old" : ""),
trial: parsedPlan.trial,
isTrial: !!parsedPlan.trial,
isOldAccount: parsedPlan.old,
isCustomer: plan?.isCustomer,
isAnnualPlan: plan?.subscriptionCycle === "yearly",
startsAt: plan?.startsAt,
endsAt: plan?.endsAt,
cancelledAt: plan?.cancelledAt,
pausedAt: plan?.pausedAt,
isPaused: plan?.isPaused ?? false,
isCancelled: !!plan?.cancelledAt,
pauseStartsAt: plan?.pauseStartsAt,
pauseEndsAt: plan?.pauseEndsAt,
discount: plan?.discount || null,
isFree: parsedPlan.plan === "free",
isStarter: parsedPlan.plan === "starter",
isPro: parsedPlan.plan === "pro",
isBusiness: parsedPlan.plan === "business",
isDatarooms:
parsedPlan.plan === "datarooms" || parsedPlan.plan === "datarooms-plus" || parsedPlan.plan === "datarooms-premium",
isDataroomsPlus: parsedPlan.plan === "datarooms-plus" || parsedPlan.plan === "datarooms-premium",
isDataroomsPremium: parsedPlan.plan === "datarooms-premium",
loading: !plan && !error && !!teamId, // Only show loading if we have a teamId but no data
error,
mutate,
};
}