Files
kovakemlak-crm/src/app/(dashboard)/settings/billing/page.tsx
T
egecankomur 3cce632eb3 feat(billing): payment infrastructure pre-prep
db: add plan, planExpiresAt, planProvider to tenant_settings (Appwrite MCP)

- schema.ts: TenantPlan type, TenantSettings plan fields
- subscription-types.ts: Emlak plan catalog (Free / Pro 499₺/ay)
- plan-limits.ts: resource limits (properties/customers/members/presentations)
  + getPlanUsage, requirePlanCapacity, PlanLimitError helpers
- subscription-actions.ts: startCheckoutAction (Polar→Shopier→mock fallback),
  activatePlanInDb / deactivatePlanInDb for webhook handlers,
  downgradeToFreeAction, getCurrentPlanAction
- /api/payments/polar/callback: verify webhook → activatePlanInDb on order/subscription events
- /api/payments/shopier/callback: verify HMAC → activate on fulfilled+paid (tenant
  email-matching TODO pending Shopier metadata support)
- /settings/billing: CurrentPlanCard with usage progress bars + UpgradeSection
- sidebar: Plan & Faturalama nav item
- PlanLimitDialog: Emlak-specific feature list
2026-05-08 15:26:18 +03:00

70 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Metadata } from "next";
import { redirect } from "next/navigation";
import { CheckCircle2, XCircle } from "lucide-react";
import { requireTenant } from "@/lib/appwrite/tenant-guard";
import { getEffectivePlan, getPlanUsage } from "@/lib/appwrite/plan-limits";
import { CurrentPlanCard } from "./components/current-plan-card";
import { UpgradeSection } from "./components/upgrade-section";
export const metadata: Metadata = {
title: "KovakEmlak — Plan & Faturalama",
};
export default async function BillingPage({
searchParams,
}: {
searchParams: Promise<Record<string, string>>;
}) {
let ctx;
try {
ctx = await requireTenant();
} catch {
redirect("/onboarding");
}
const params = await searchParams;
const upgraded = params.upgraded === "1";
const downgraded = params.downgraded === "1";
const plan = getEffectivePlan(ctx);
const { usage } = await getPlanUsage(ctx);
const officeName = ctx.settings?.officeName ?? "Ofis";
return (
<div className="flex-1 space-y-6 px-6 pt-0">
<div className="flex flex-col gap-1">
<p className="text-muted-foreground text-sm">{officeName}</p>
<h1 className="text-2xl font-bold tracking-tight">Plan & Faturalama</h1>
<p className="text-muted-foreground text-sm">
Mevcut planınızı görüntüleyin ve yönetin.
</p>
</div>
{upgraded && (
<div className="flex items-center gap-2 rounded-md border border-green-500/30 bg-green-500/10 px-4 py-3 text-sm text-green-700 dark:text-green-400">
<CheckCircle2 className="h-4 w-4 shrink-0" />
Pro plana başarıyla geçtiniz. İyi kullanımlar!
</div>
)}
{downgraded && (
<div className="flex items-center gap-2 rounded-md border border-muted bg-muted/40 px-4 py-3 text-sm text-muted-foreground">
<XCircle className="h-4 w-4 shrink-0" />
Ücretsiz plana geçildi.
</div>
)}
<div className="grid gap-6 md:grid-cols-2">
<CurrentPlanCard
plan={plan}
expiresAt={ctx.settings?.planExpiresAt ?? null}
usage={usage}
/>
<UpgradeSection currentPlan={plan} />
</div>
</div>
);
}