From 3110b40cf94f67af2bf7bd07d2227781f63cb712 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 29 May 2026 23:28:45 +0200 Subject: [PATCH] fix(onboarding): explain blocked Next, humanise errors, de-jargon provisioning --- .../onboarding/provisioning-status.tsx | 48 ++++++---- src/components/onboarding/wizard.tsx | 93 ++++++++++++++----- 2 files changed, 99 insertions(+), 42 deletions(-) diff --git a/src/components/onboarding/provisioning-status.tsx b/src/components/onboarding/provisioning-status.tsx index 6dd877c..8f959b7 100644 --- a/src/components/onboarding/provisioning-status.tsx +++ b/src/components/onboarding/provisioning-status.tsx @@ -432,25 +432,35 @@ export function ProvisioningStatus({ requestId, canAct }: Props) { {t("phase")} - {conditions.map((c, i) => ( -
- {c.type} - - {c.reason || c.status} - -
- ))} + {/* Setup progress. The operator reports readiness as a list of + internal K8s conditions (OpenBao policy, LiteLLM key, network + policy, …) — meaningful to operators, jargon to customers. + We surface the *shape* of that progress (how many steps are + done) without leaking the internal names. */} + {conditions.length > 0 && + (() => { + const done = conditions.filter((c) => c.status === "True").length; + const total = conditions.length; + const pct = Math.round((done / total) * 100); + return ( +
+
+ + {t("setupProgress")} + + + {t("setupStepsComplete", { done, total })} + +
+
+
+
+
+ ); + })()}
); diff --git a/src/components/onboarding/wizard.tsx b/src/components/onboarding/wizard.tsx index 3703733..abfcf18 100644 --- a/src/components/onboarding/wizard.tsx +++ b/src/components/onboarding/wizard.tsx @@ -420,18 +420,51 @@ export function OnboardingWizard({ [] ); - // Validate that all secret-requiring enabled packages have complete credentials - const packageCredentialsValid = (): boolean => { + // Enabled packages that still need something from the user before the + // configure step can advance — a missing credential field or an + // unaccepted disclaimer. Returns the package defs so the UI can name + // exactly what's blocking the (otherwise silently disabled) Next + // button instead of greying it out with no explanation. + const incompletePackages = (): PackageDef[] => { + const out: PackageDef[] = []; for (const pkgId of config.packages) { const def = PACKAGE_CATALOG.find((p) => p.id === pkgId); - if (!def?.requiresSecrets) continue; - const secrets = packageSecrets[pkgId] || {}; - for (const field of def.secrets || []) { - if (!secrets[field.key]?.trim()) return false; + if (!def) continue; + let incomplete = false; + if (def.requiresSecrets) { + const secrets = packageSecrets[pkgId] || {}; + for (const field of def.secrets || []) { + if (!secrets[field.key]?.trim()) { + incomplete = true; + break; + } + } } - if (def.disclaimerKey && !disclaimerAccepted[pkgId]) return false; + if (def.disclaimerKey && !disclaimerAccepted[pkgId]) incomplete = true; + if (incomplete) out.push(def); } - return true; + return out; + }; + + const packageCredentialsValid = (): boolean => + incompletePackages().length === 0; + + // Map zod field paths to human labels for the confirm-step error + // summary, so a stray validation failure reads "Postal code" rather + // than "billingAddress.postalCode". Unknown paths fall back to the + // raw path (this defence-in-depth list should rarely render at all). + const fieldLabel = (path: string): string => { + const map: Record = { + instanceName: t("instanceName"), + agentName: t("agentName"), + "billingAddress.company": t("billingCompany"), + "billingAddress.street": t("billingStreet"), + "billingAddress.postalCode": t("billingPostalCode"), + "billingAddress.city": t("billingCity"), + "billingAddress.country": t("billingCountry"), + "billingAddress.vatNumber": t("billingVatNumber"), + }; + return map[path] ?? path; }; const handleSubmit = async () => { @@ -984,20 +1017,33 @@ export function OnboardingWizard({ -
- - +
+ {(() => { + const blocking = incompletePackages(); + if (blocking.length === 0) return null; + return ( +

+ {t("packagesIncompleteHint", { + packages: blocking.map((p) => p.name).join(", "), + })} +

+ ); + })()} +
+ + +
)} @@ -1380,7 +1426,8 @@ export function OnboardingWizard({
    {Object.entries(errors).map(([path, msg]) => (
  • - {path}: {msg} + {fieldLabel(path)}:{" "} + {msg}
  • ))}