Group B fixes
All checks were successful
Build and Push / build (push) Successful in 1m24s

This commit is contained in:
2026-04-29 15:43:12 +02:00
parent c7df5c83a4
commit eeef108f7e
18 changed files with 387 additions and 82 deletions

View File

@@ -40,11 +40,14 @@ function NavBar() {
<NavLink href="/dashboard" active={pathname === "/dashboard"}>
{t("dashboard")}
</NavLink>
{/* Slice 7: /team is owner+platform only. Match server-side
gate (canMutate). The roles array carries either "owner"
or "user" for customer sessions; isPlatform covers the
platform side. */}
{/* Slice 7: /team is owner+platform only AND personal
accounts are excluded — they have no team to manage
(Bug 8). Match server-side gates (`canMutate`,
`user.isPersonal === false`). The roles array carries
either "owner" or "user" for customer sessions;
isPlatform covers the platform side. */}
{user &&
!user.isPersonal &&
(user.isPlatform ||
(Array.isArray(user.roles) && user.roles.includes("owner"))) && (
<NavLink href="/team" active={pathname === "/team"}>
@@ -62,8 +65,17 @@ function NavBar() {
{/* Right side */}
<div className="flex items-center gap-4">
{user && (
// For personal accounts the orgName is opaque
// ("personal-3f2a8b1c") or a synthetic legacy
// "Name (Personal)" — neither is what we want in the nav.
// Show the user's display name instead. The detection logic
// and fallback chain live in `lib/personal-org.ts`; keeping
// a thin inline branch here avoids importing a server-only
// helper into a client component.
<span className="hidden md:inline text-xs text-text-secondary font-mono">
{user.orgName}
{user.isPersonal
? user.name || (user.email ? user.email.split("@")[0] : user.orgName)
: user.orgName}
</span>
)}
<LanguageSwitcher />

View File

@@ -5,6 +5,13 @@ import { OnboardingWizard } from "./wizard";
interface OnboardingFlowProps {
orgName: string;
/**
* The user's display name. Forwarded to the wizard so personal
* accounts can show the user's own name where they would otherwise
* see an opaque org name. Ignored for company accounts.
*/
userName?: string;
userEmail?: string;
}
/**
@@ -18,12 +25,18 @@ interface OnboardingFlowProps {
* level (which renders one `<ProvisioningStatus>` per pending request),
* so this wrapper does just one thing: show the wizard, then navigate.
*/
export function OnboardingFlow({ orgName }: OnboardingFlowProps) {
export function OnboardingFlow({
orgName,
userName,
userEmail,
}: OnboardingFlowProps) {
const router = useRouter();
return (
<OnboardingWizard
orgName={orgName}
userName={userName}
userEmail={userEmail}
onComplete={() => {
// Navigate back to /dashboard and re-fetch on the server. The
// parent server component will see the new `pending` row and

View File

@@ -4,7 +4,7 @@ import { useState, useCallback, useEffect, useRef } from "react";
import { useTranslations } from "next-intl";
import { Card } from "@/components/ui/card";
import { PACKAGE_CATALOG, type PackageDef } from "@/lib/packages";
import { isPersonalOrgName, PERSONAL_ORG_SUFFIX } from "@/lib/personal-org";
import { isPersonalOrgName, displayOrgNameFor } from "@/lib/personal-org";
type Step = "welcome" | "configure" | "billing" | "confirm";
@@ -48,23 +48,40 @@ const CATEGORIES = [
interface WizardProps {
orgName: string;
/**
* The user's display name. Used as the visible label for personal
* accounts (where `orgName` is an opaque ID like "personal-3f2a8b1c"
* or a synthetic legacy "{name} (Personal)" string). Ignored for
* company accounts.
*/
userName?: string;
userEmail?: string;
onComplete: () => void;
}
export function OnboardingWizard({ orgName, onComplete }: WizardProps) {
export function OnboardingWizard({
orgName,
userName,
userEmail,
onComplete,
}: WizardProps) {
const t = useTranslations("onboarding");
const tPkg = useTranslations("packages");
const tCommon = useTranslations("common");
// Slice 4: personal accounts have an org name of the form
// "{givenName} {familyName} (Personal)". For SOUL.md and the billing
// company line, strip the suffix so the visible string is the user's
// actual name (no stray "(Personal)" leaking onto invoices or into
// the assistant's prompt).
// Personal accounts have an org name that is either the legacy
// "{givenName} {familyName} (Personal)" or the current opaque
// "personal-{8hex}" form. Either way, the customer-facing display
// should be the user's own name — never the org name. SOUL.md
// interpolation and the billing form follow the same rule so
// invoices and prompts don't leak "(Personal)" or "personal-3f2a..".
const isPersonal = isPersonalOrgName(orgName);
const displayOrgName = isPersonal
? orgName.slice(0, -PERSONAL_ORG_SUFFIX.length).trim()
: orgName;
const displayOrgName = displayOrgNameFor({
name: userName,
email: userEmail,
orgName,
isPersonal,
});
const [step, setStep] = useState<Step>("welcome");
const [submitting, setSubmitting] = useState(false);