Files
pieced-portal/src/app/[locale]/dashboard/edit/[id]/page.tsx
admin 219b4c8365
Some checks failed
Build and Push / build (push) Failing after 37s
Group D fixes
2026-04-29 22:13:08 +02:00

88 lines
3.2 KiB
TypeScript

import { getSessionUser, canMutate } from "@/lib/session";
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { getTenantRequestById } from "@/lib/db";
import { OnboardingFlow } from "@/components/onboarding/onboarding-flow";
import { BackLink } from "@/components/ui/back-link";
/**
* /dashboard/edit/[id] — re-opens the onboarding wizard with the
* fields of a still-pending request pre-filled (Bug 6). On submit,
* the wizard PATCHes /api/onboarding/[id] instead of POSTing to
* /api/onboarding.
*
* Hard guards
* -----------
* - Logged-in customer owner (or platform user) only — same as the
* /dashboard/new page.
* - Request must exist, belong to the caller's org, and be in 'pending'
* status. Editing approved/provisioning rows would race against the
* operator; we redirect such cases back to the dashboard rather than
* render an invalid wizard.
*
* Pre-fill
* --------
* The wizard takes a single `editingRequest` prop — when present, it
* (a) pre-populates state from those values and (b) targets the PATCH
* endpoint on submit. When absent, it behaves exactly as today (POST
* to /api/onboarding).
*
* Note on encrypted secrets
* -------------------------
* Per-package secrets are NEVER decrypted server-side and exposed to
* the client (would be a clear security regression). When editing,
* the wizard opens with empty secret fields and the user re-enters
* any they want to change. If they don't touch the package-secrets
* UI, the existing encrypted blob in the DB is preserved by the
* PATCH endpoint (it only re-encrypts when the wizard sends a
* non-empty secrets payload).
*/
export default async function EditRequestPage({
params,
}: {
params: Promise<{ id: string; locale: string }>;
}) {
const { id } = await params;
const user = await getSessionUser();
if (!user) redirect("/login");
if (user.isPlatform) redirect("/dashboard");
if (!canMutate(user)) redirect("/dashboard");
const tr = await getTenantRequestById(id);
if (!tr) redirect("/dashboard");
if (tr.zitadelOrgId !== user.orgId) redirect("/dashboard");
if (tr.status !== "pending") redirect("/dashboard");
const t = await getTranslations("dashboard");
const tOnboarding = await getTranslations("onboarding");
return (
<div className="container max-w-3xl mx-auto px-4 py-8">
<div className="mb-8 animate-in">
<BackLink href="/dashboard" label={t("title")} />
<h1 className="font-display text-2xl font-semibold accent-rule mb-2">
{tOnboarding("editRequestTitle")}
</h1>
<p className="text-sm text-text-secondary">
{tOnboarding("editRequestDescription")}
</p>
</div>
<OnboardingFlow
orgName={user.orgName}
userName={user.name}
userEmail={user.email}
editingRequest={{
id: tr.id,
instanceName: tr.instanceName ?? "",
agentName: tr.agentName,
soulMd: tr.soulMd ?? "",
agentsMd: tr.agentsMd ?? "",
packages: tr.packages,
billingAddress: tr.billingAddress,
billingNotes: tr.billingNotes ?? "",
}}
/>
</div>
);
}