From bad67f66c41cf9aa0673769f8ea98df909e9dfda Mon Sep 17 00:00:00 2001 From: Hardik Date: Sun, 21 Jun 2026 01:45:59 +0530 Subject: [PATCH] feat(companies): move add/edit from dialog to dedicated pages The company form outgrew the modal once the branding (logo/stamp) section was added. Add/edit now live on their own routes: - /admin/companies/new - /admin/companies/[id]/edit - createCompany returns the new id and the create flow lands on the edit page so logo/stamp can be uploaded immediately - list "+ Add Company" is a link; row "Edit" navigates to the edit page - branding is its own card on the edit page (independent uploads) - list page no longer mints a presigned URL per company (moved to edit) Co-Authored-By: Claude Opus 4.8 --- .../admin/companies/[id]/edit/page.tsx | 39 ++++ App/app/(portal)/admin/companies/actions.ts | 6 +- .../admin/companies/companies-table.tsx | 15 +- .../(portal)/admin/companies/company-form.tsx | 173 +++++++----------- App/app/(portal)/admin/companies/new/page.tsx | 15 ++ App/app/(portal)/admin/companies/page.tsx | 35 ++-- 6 files changed, 151 insertions(+), 132 deletions(-) create mode 100644 App/app/(portal)/admin/companies/[id]/edit/page.tsx create mode 100644 App/app/(portal)/admin/companies/new/page.tsx diff --git a/App/app/(portal)/admin/companies/[id]/edit/page.tsx b/App/app/(portal)/admin/companies/[id]/edit/page.tsx new file mode 100644 index 0000000..b4ff93c --- /dev/null +++ b/App/app/(portal)/admin/companies/[id]/edit/page.tsx @@ -0,0 +1,39 @@ +import { auth } from "@/auth"; +import { db } from "@/lib/db"; +import { hasPermission } from "@/lib/permissions"; +import { generateDownloadUrl } from "@/lib/storage"; +import { redirect, notFound } from "next/navigation"; +import { CompanyForm } from "../../company-form"; +import type { Metadata } from "next"; + +export const metadata: Metadata = { title: "Edit Company" }; + +export default async function EditCompanyPage({ params }: { params: Promise<{ id: string }> }) { + const session = await auth(); + if (!session?.user) redirect("/login"); + if (!hasPermission(session.user.role, "manage_vessels_accounts")) redirect("/dashboard"); + + const { id } = await params; + const c = await db.company.findUnique({ where: { id } }); + if (!c) notFound(); + + return ( + + ); +} diff --git a/App/app/(portal)/admin/companies/actions.ts b/App/app/(portal)/admin/companies/actions.ts index a0c867b..bcd68fb 100644 --- a/App/app/(portal)/admin/companies/actions.ts +++ b/App/app/(portal)/admin/companies/actions.ts @@ -30,7 +30,7 @@ const companySchema = z.object({ invoiceAddress: z.string().optional(), }); -export async function createCompany(formData: FormData): Promise { +export async function createCompany(formData: FormData): Promise<{ ok: true; id: string } | { error: string }> { const session = await auth(); if (!session?.user || !hasPermission(session.user.role, "manage_vessels_accounts")) { return { error: "Unauthorized" }; @@ -54,11 +54,11 @@ export async function createCompany(formData: FormData): Promise { const conflict = await db.company.findFirst({ where: { code: { equals: code, mode: "insensitive" } } }); if (conflict) return { error: `Code "${code.toUpperCase()}" is already used by another company.` }; } - await db.company.create({ + const created = await db.company.create({ data: { name, code: code?.toUpperCase() ?? null, gstNumber: gstNumber ?? null, address: address ?? null, telephone: telephone ?? null, mobile: mobile ?? null, email: email || null, invoiceEmail: invoiceEmail || null, invoiceAddress: invoiceAddress ?? null }, }); revalidatePath("/admin/companies"); - return { ok: true }; + return { ok: true, id: created.id }; } export async function updateCompany(formData: FormData): Promise { diff --git a/App/app/(portal)/admin/companies/companies-table.tsx b/App/app/(portal)/admin/companies/companies-table.tsx index 167df7d..70384af 100644 --- a/App/app/(portal)/admin/companies/companies-table.tsx +++ b/App/app/(portal)/admin/companies/companies-table.tsx @@ -1,7 +1,8 @@ "use client"; import { useState } from "react"; -import { AddCompanyButton, EditCompanyButton } from "./company-form"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; import { RowActionsMenu, RowActionsItem, RowActionsDestructiveItem, RowActionsSeparator } from "@/components/ui/row-actions-menu"; import { DeleteConfirmDialog } from "@/components/ui/delete-confirm-dialog"; import { ConfirmDialog } from "@/components/ui/confirm-dialog"; @@ -18,27 +19,24 @@ export type CompanyRow = { email: string | null; invoiceEmail: string | null; invoiceAddress: string | null; - logoUrl: string | null; - stampUrl: string | null; isActive: boolean; }; function CompanyActionsMenu({ company }: { company: CompanyRow }) { - const [editOpen, setEditOpen] = useState(false); + const router = useRouter(); const [deleteOpen, setDeleteOpen] = useState(false); const [toggleOpen, setToggleOpen] = useState(false); return ( <> - setEditOpen(true)}>Edit + router.push(`/admin/companies/${company.id}/edit`)}>Edit setToggleOpen(true)}> {company.isActive ? "Deactivate" : "Activate"} setDeleteOpen(true)}>Delete - deleteCompany(company.id)} @@ -62,7 +60,10 @@ export function CompaniesTable({ companies }: { companies: CompanyRow[] }) {

Company Management

Sister companies used for invoicing and purchase orders

- + + + Add Company +
diff --git a/App/app/(portal)/admin/companies/company-form.tsx b/App/app/(portal)/admin/companies/company-form.tsx index 293548d..467f7c5 100644 --- a/App/app/(portal)/admin/companies/company-form.tsx +++ b/App/app/(portal)/admin/companies/company-form.tsx @@ -2,11 +2,12 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; -import { AdminDialog } from "@/components/ui/admin-dialog"; +import Link from "next/link"; +import { ArrowLeft } from "lucide-react"; import { createCompany, updateCompany } from "./actions"; import { CompanyBrandingUploader } from "./company-branding-uploader"; -type CompanyRow = { +export type CompanyFormData = { id: string; name: string; code: string | null; @@ -25,7 +26,7 @@ type CompanyRow = { const INPUT = "w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20"; const LABEL = "block text-xs font-medium text-neutral-700 mb-1"; -function CompanyFormFields({ company }: { company?: CompanyRow }) { +function CompanyFormFields({ company }: { company?: CompanyFormData }) { return (
@@ -70,117 +71,83 @@ function CompanyFormFields({ company }: { company?: CompanyRow }) {