Files
kovakemlak-crm/src/lib/appwrite/customer-actions.ts
T
egecankomur 933cb17107 feat: plan/limit system + role-based page access
Plan & limit enforcement:
- src/lib/plans.ts: PLAN_LIMITS (free: 15 props, 25 customers, 5 presentations, 1 member, 5 imgs/prop)
- checkLimit() + limitErrorMessage() — role-aware error messages (owner: upgrade CTA, others: contact admin)
- Create actions (property, customer, presentation, team invite): hard limit check before create
- PropertyImageUploader: maxImages prop + isOwner-aware toast
- UsageBadge component: usage counter shown only to owner (green→amber→red)

Role-based access:
- TenantContext + ActiveContext: add memberCount + role fields
- Dashboard layout: non-owner on free plan with >1 member → /plan-limit
- /plan-limit: blocked-access page with owner contact info + sign-out
- AppSidebar: minRole filtering — Plan & Faturalama (owner only), Çalışma Alanı/Yatırımcılar (admin+)
- settings/billing: owner-only hard redirect
- settings/workspace + settings/members: member redirect, admin read-only
- settings/investors: member redirect
- workspace-actions + logo-actions: restricted to owner only
- Workspace form: canEdit = owner only (admin sees read-only view)
2026-05-12 18:46:02 +03:00

131 lines
3.6 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.
"use server";
import { ID, Permission, Role, Query } from "node-appwrite";
import { revalidatePath } from "next/cache";
import { customerSchema } from "@/lib/validation/customers";
import { DATABASE_ID, TABLES, type CustomerStage } from "./schema";
import { createAdminClient } from "./server";
import { requireTenant } from "@/lib/appwrite/tenant-guard";
import { checkLimit, limitErrorMessage } from "@/lib/plans";
type ActionState = { ok: boolean; error?: string; fieldErrors?: Record<string, string[]> };
export async function createCustomerAction(
_prev: ActionState,
formData: FormData,
): Promise<ActionState> {
const ctx = await requireTenant();
const raw = Object.fromEntries(formData.entries());
const parsed = customerSchema.safeParse(raw);
if (!parsed.success) {
return { ok: false, fieldErrors: parsed.error.flatten().fieldErrors };
}
const limitCheck = await checkLimit(ctx.tenantId, ctx.settings?.plan, "customers");
if (!limitCheck.allowed) {
return { ok: false, error: limitErrorMessage("customers", limitCheck.limit, ctx.role) };
}
const { tablesDB } = createAdminClient();
const data = parsed.data;
try {
await tablesDB.createRow(
DATABASE_ID,
TABLES.customers,
ID.unique(),
{
tenantId: ctx.tenantId,
name: data.name,
email: data.email,
phone: data.phone,
type: data.type,
stage: data.stage ?? "ilk_temas",
source: data.source,
nextFollowUpDate: data.nextFollowUpDate,
assigneeId: data.assigneeId,
notes: data.notes,
createdBy: ctx.user.id,
},
[
Permission.read(Role.team(ctx.tenantId)),
Permission.update(Role.team(ctx.tenantId)),
Permission.delete(Role.team(ctx.tenantId, "owner")),
Permission.delete(Role.team(ctx.tenantId, "admin")),
],
);
} catch {
return { ok: false, error: "Müşteri oluşturulamadı." };
}
revalidatePath("/customers");
return { ok: true };
}
export async function updateCustomerAction(
id: string,
_prev: ActionState,
formData: FormData,
): Promise<ActionState> {
await requireTenant();
const raw = Object.fromEntries(formData.entries());
const parsed = customerSchema.safeParse(raw);
if (!parsed.success) {
return { ok: false, fieldErrors: parsed.error.flatten().fieldErrors };
}
const { tablesDB } = createAdminClient();
const data = parsed.data;
try {
await tablesDB.updateRow(DATABASE_ID, TABLES.customers, id, {
name: data.name,
email: data.email,
phone: data.phone,
type: data.type,
stage: data.stage,
source: data.source,
nextFollowUpDate: data.nextFollowUpDate,
assigneeId: data.assigneeId,
notes: data.notes,
});
} catch {
return { ok: false, error: "Müşteri güncellenemedi." };
}
revalidatePath("/customers");
return { ok: true };
}
export async function updateCustomerStageAction(
id: string,
stage: CustomerStage,
): Promise<ActionState> {
await requireTenant();
const { tablesDB } = createAdminClient();
try {
await tablesDB.updateRow(DATABASE_ID, TABLES.customers, id, { stage });
} catch {
return { ok: false, error: "Aşama güncellenemedi." };
}
revalidatePath("/customers");
return { ok: true };
}
export async function deleteCustomerAction(id: string): Promise<ActionState> {
await requireTenant();
const { tablesDB } = createAdminClient();
try {
await tablesDB.deleteRow(DATABASE_ID, TABLES.customers, id);
} catch {
return { ok: false, error: "Müşteri silinemedi." };
}
revalidatePath("/customers");
return { ok: true };
}