Fix tenant creation page error handling and logging
Some checks failed
CI - Production Readiness / Verify (push) Has been cancelled
Some checks failed
CI - Production Readiness / Verify (push) Has been cancelled
This commit is contained in:
@ -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 />
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user