feat: all core modules — properties, customers, searches, matches, presentations, activities, investors + public sunum page

- Server actions: property/customer/search/presentation/activity/investor CRUD
- Matching engine: matchPropertyToSearches + syncMatchesForSearch on search save
- UI: form sheets + table clients for all modules
- Public /sunum/[token] page (no auth) with property card grid + expiry check
- All pages force-dynamic for auth guard compatibility
This commit is contained in:
egecankomur
2026-05-05 12:03:48 +03:00
parent 2f17c342ca
commit 4ef0482732
34 changed files with 3174 additions and 36 deletions
+143
View File
@@ -0,0 +1,143 @@
"use server";
import { ID, Permission, Role } from "node-appwrite";
import { revalidatePath } from "next/cache";
import { propertySchema } from "@/lib/validation/properties";
import { DATABASE_ID, TABLES, type Property } from "./schema";
import { createAdminClient } from "./server";
import { requireTenant } from "@/lib/appwrite/tenant-guard";
import { matchPropertyToSearches } from "./matching";
type ActionState = { ok: boolean; error?: string; fieldErrors?: Record<string, string[]> };
export async function createPropertyAction(
_prev: ActionState,
formData: FormData,
): Promise<ActionState> {
const ctx = await requireTenant();
const raw = Object.fromEntries(formData.entries());
const parsed = propertySchema.safeParse(raw);
if (!parsed.success) {
return { ok: false, fieldErrors: parsed.error.flatten().fieldErrors };
}
const { tablesDB } = createAdminClient();
const id = ID.unique();
const data = parsed.data;
try {
const row = await tablesDB.createRow(
DATABASE_ID,
TABLES.properties,
id,
{
tenantId: ctx.tenantId,
title: data.title,
description: data.description,
propertyType: data.propertyType,
listingType: data.listingType,
status: data.status ?? "aktif",
price: data.price,
currency: data.currency ?? "TRY",
roomCount: data.roomCount,
grossM2: data.grossM2,
netM2: data.netM2,
floor: data.floor,
totalFloors: data.totalFloors,
buildingAge: data.buildingAge,
city: data.city,
district: data.district,
neighborhood: data.neighborhood,
address: data.address,
mapLat: data.mapLat,
mapLng: data.mapLng,
featuresJson: data.featuresJson,
imageIds: data.imageIds,
createdBy: ctx.user.id,
assigneeId: data.assigneeId,
},
[
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")),
],
);
await matchPropertyToSearches(row as unknown as Property, ctx.tenantId, ctx.user.id).catch(
() => {},
);
} catch {
return { ok: false, error: "İlan oluşturulamadı." };
}
revalidatePath("/properties");
return { ok: true };
}
export async function updatePropertyAction(
id: string,
_prev: ActionState,
formData: FormData,
): Promise<ActionState> {
const ctx = await requireTenant();
const raw = Object.fromEntries(formData.entries());
const parsed = propertySchema.safeParse(raw);
if (!parsed.success) {
return { ok: false, fieldErrors: parsed.error.flatten().fieldErrors };
}
const { tablesDB } = createAdminClient();
const data = parsed.data;
try {
const row = await tablesDB.updateRow(DATABASE_ID, TABLES.properties, id, {
title: data.title,
description: data.description,
propertyType: data.propertyType,
listingType: data.listingType,
status: data.status,
price: data.price,
currency: data.currency,
roomCount: data.roomCount,
grossM2: data.grossM2,
netM2: data.netM2,
floor: data.floor,
totalFloors: data.totalFloors,
buildingAge: data.buildingAge,
city: data.city,
district: data.district,
neighborhood: data.neighborhood,
address: data.address,
mapLat: data.mapLat,
mapLng: data.mapLng,
featuresJson: data.featuresJson,
imageIds: data.imageIds,
assigneeId: data.assigneeId,
});
await matchPropertyToSearches(row as unknown as Property, ctx.tenantId, ctx.user.id).catch(
() => {},
);
} catch {
return { ok: false, error: "İlan güncellenemedi." };
}
revalidatePath("/properties");
return { ok: true };
}
export async function deletePropertyAction(id: string): Promise<ActionState> {
await requireTenant();
const { tablesDB } = createAdminClient();
try {
await tablesDB.deleteRow(DATABASE_ID, TABLES.properties, id);
} catch {
return { ok: false, error: "İlan silinemedi." };
}
revalidatePath("/properties");
return { ok: true };
}