feat: plan tier system, mock checkout, saved cards, tenant logo upload + mobile sheet fix
Plan & billing layer:
- New tables: subscription_payments, saved_cards (via Appwrite MCP)
- tenant_settings: plan/planStartedAt/planExpiresAt/lastPaymentId columns
- Free tier limits (50 customers / 100 finance entries / 5 software / 1 member)
enforced via requirePlanCapacity gate in create actions
- PlanLimitDialog opens when limit hit; UsageBanner at 80% threshold
- /pricing rebuilt with Free + Pro tiers and Klinik/Ajans ecosystem teasers
- /settings/billing redesigned: compact plan summary, saved cards list,
KVKK transparency block, payment history
- Usage stats moved to /pricing where they are decision-relevant
Mock checkout flow:
- 3D animated credit card with sync inputs and CVC flip
- Brand auto-detection (Visa / Mastercard / Amex / troy)
- Saved-card mode when previous cards exist; first card defaults to default
- 'Bu kartı kaydet' checkbox with explicit storage scope disclosure
- /settings/billing/checkout/[orderId] route
Saved cards:
- saved_cards bucket stores last4 + brand + expiry + holder only
- Default toggle, remove action, owner-only management
- Architecture ready for Shopier provider token swap-in
Tenant logo upload (first file upload feature):
- New Appwrite bucket: tenant-logos (max 2MB, image only, public read)
- uploadLogoAction with orphan cleanup, removeLogoAction
- LogoUploader UI: drag-drop, client-side preview, validation
- Sidebar shows logo when set, falls back to default icon
Mobile sheet fix:
- SheetContent uses h-dvh instead of h-full (dynamic viewport)
- SheetFooter pads pb-[max(1rem,env(safe-area-inset-bottom))]
- 13 form sheets switched py-4 → pt-4 to let safe-area apply
db: subscription_payments, saved_cards tables; tenant_settings plan columns;
tenant-logos storage bucket
This commit is contained in:
@@ -5,6 +5,11 @@ import { AppwriteException, ID, Permission, Role } from "node-appwrite";
|
||||
import { z } from "zod";
|
||||
|
||||
import { logAudit } from "./audit";
|
||||
import {
|
||||
isPlanLimitError,
|
||||
planLimitMessage,
|
||||
requirePlanCapacity,
|
||||
} from "./plan-limits";
|
||||
import { DATABASE_ID, TABLES, type Customer } from "./schema";
|
||||
import { createAdminClient } from "./server";
|
||||
import { requireTenant } from "./tenant-guard";
|
||||
@@ -64,6 +69,19 @@ export async function createCustomerAction(
|
||||
return { ok: false, error: "Form geçersiz.", fieldErrors: flattenErrors(parsed.error) };
|
||||
}
|
||||
|
||||
try {
|
||||
await requirePlanCapacity(ctx, "customers");
|
||||
} catch (e) {
|
||||
if (isPlanLimitError(e)) {
|
||||
return {
|
||||
ok: false,
|
||||
error: planLimitMessage(e.resource, e.limit),
|
||||
code: "PLAN_LIMIT_EXCEEDED",
|
||||
};
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
const { tablesDB } = createAdminClient();
|
||||
const row = await tablesDB.createRow(
|
||||
|
||||
Reference in New Issue
Block a user