Fix tenant creation page error handling and logging
Some checks failed
CI - Production Readiness / Verify (push) Has been cancelled

This commit is contained in:
2026-04-21 20:02:38 +07:00
parent 311551c31a
commit f48c87e36d
2 changed files with 73 additions and 26 deletions

View File

@ -15,10 +15,22 @@ export default async function NewTenantPage({
redirect("/unauthorized"); redirect("/unauthorized");
} }
const [plans, params] = await Promise.all([ const params = await (searchParams ?? Promise.resolve({ error: undefined }));
prisma.subscriptionPlan.findMany({ orderBy: { priceMonthly: "asc" } }), let plans: Array<{
searchParams ?? Promise.resolve({ error: undefined }) id: string;
]); name: string;
code: string;
priceMonthly: number;
}> = [];
try {
plans = await prisma.subscriptionPlan.findMany({ orderBy: { priceMonthly: "asc" } });
} catch (error) {
console.error("[tenant/new] load plans failed", {
actorUserId: session.userId,
error: error instanceof Error ? error.message : String(error)
});
}
const error = params.error; const error = params.error;
const errorMessage = const errorMessage =
@ -30,6 +42,10 @@ export default async function NewTenantPage({
? "Slug tenant sudah dipakai." ? "Slug tenant sudah dipakai."
: error === "admin_email_exists" : error === "admin_email_exists"
? "Email admin awal sudah terpakai." ? "Email admin awal sudah terpakai."
: error === "tenant_creation_failed"
? "Tidak bisa membuat tenant, silakan cek log server untuk detail error."
: error === "plans_fetch_failed"
? "Tidak dapat memuat daftar plan. Cek log server/DB koneksi."
: null; : null;
return ( return (
@ -37,6 +53,11 @@ export default async function NewTenantPage({
<SectionCard title="Tenant form"> <SectionCard title="Tenant form">
<form action={createTenant} className="grid gap-4 md:max-w-3xl md:grid-cols-2"> <form action={createTenant} className="grid gap-4 md:max-w-3xl md:grid-cols-2">
{errorMessage ? <p className="col-span-2 rounded-xl border border-warning/30 bg-warning/10 p-3 text-sm text-warning">{errorMessage}</p> : null} {errorMessage ? <p className="col-span-2 rounded-xl border border-warning/30 bg-warning/10 p-3 text-sm text-warning">{errorMessage}</p> : null}
{plans.length === 0 ? (
<p className="col-span-2 rounded-xl border border-warning/30 bg-warning/10 p-3 text-sm text-warning">
Belum ada data plan. Isi dulu plan pada <a href="/super-admin/billing/plans" className="underline">Catalog Plan</a> sebelum membuat tenant.
</p>
) : null}
<input name="name" className="rounded-xl border border-line px-4 py-3" placeholder="Company name" required /> <input name="name" className="rounded-xl border border-line px-4 py-3" placeholder="Company name" required />
<input name="slug" className="rounded-xl border border-line px-4 py-3" placeholder="Tenant slug" required /> <input name="slug" className="rounded-xl border border-line px-4 py-3" placeholder="Tenant slug" required />
<input name="timezone" className="rounded-xl border border-line px-4 py-3" placeholder="Timezone" required /> <input name="timezone" className="rounded-xl border border-line px-4 py-3" placeholder="Timezone" required />

View File

@ -1153,13 +1153,13 @@ export async function createTenant(formData: FormData) {
const session = await requireSuperAdminSession(); const session = await requireSuperAdminSession();
const auditContext = await getAuditContext(session); const auditContext = await getAuditContext(session);
const name = formValue(formData, "name"); const name = formValue(formData, "name").trim();
const companyName = formValue(formData, "companyName") || name; const companyName = (formValue(formData, "companyName") || name).trim();
const slug = formValue(formData, "slug").toLowerCase(); const slug = formValue(formData, "slug").trim().toLowerCase();
const timezone = formValue(formData, "timezone"); const timezone = formValue(formData, "timezone").trim();
const planId = formValue(formData, "planId"); const planId = formValue(formData, "planId");
const adminFullName = formValue(formData, "adminFullName") || `Admin ${name}`; const adminFullName = (formValue(formData, "adminFullName") || `Admin ${name}`).trim();
const adminEmail = formValue(formData, "adminEmail"); const adminEmail = formValue(formData, "adminEmail").trim() || undefined;
const adminPassword = formValue(formData, "adminPassword"); const adminPassword = formValue(formData, "adminPassword");
if (!name || !slug || !timezone || !planId) { if (!name || !slug || !timezone || !planId) {
@ -1191,17 +1191,33 @@ export async function createTenant(formData: FormData) {
adminEmail: string; adminEmail: string;
}; };
const tenantCreation = await prisma.$transaction(async (tx) => { let tenantCreation: {
const createdTenant = await tx.tenant.create({ tenant: {
data: { id: string;
name, name: string;
companyName: companyName || name, slug: string;
slug, planId: string;
timezone, timezone: string;
status: TenantStatus.ACTIVE, status: TenantStatus;
planId createdAt: Date;
} updatedAt: Date;
}); companyName: string;
};
adminInvite: AdminInvitePayload | null;
};
try {
tenantCreation = await prisma.$transaction(async (tx) => {
const createdTenant = await tx.tenant.create({
data: {
name,
companyName: companyName || name,
slug,
timezone,
status: TenantStatus.ACTIVE,
planId
}
});
if (adminEmail) { if (adminEmail) {
const shouldInviteAdmin = !adminPassword; const shouldInviteAdmin = !adminPassword;
@ -1243,11 +1259,21 @@ export async function createTenant(formData: FormData) {
} }
} }
return { return {
tenant: createdTenant, tenant: createdTenant,
adminInvite: null adminInvite: null
}; };
}); });
} catch (error) {
console.error("[createTenant] transaction failed", {
actorUserId: session.userId,
tenantName: name,
tenantSlug: slug,
planId,
error: error instanceof Error ? error.message : String(error)
});
redirect("/super-admin/tenants/new?error=tenant_creation_failed");
}
const tenant = tenantCreation.tenant; const tenant = tenantCreation.tenant;
const adminInvite = tenantCreation.adminInvite; const adminInvite = tenantCreation.adminInvite;