diff --git a/src/app/(admin)/admin/settings/page.tsx b/src/app/(admin)/admin/settings/page.tsx index 947b5dc..79207a9 100644 --- a/src/app/(admin)/admin/settings/page.tsx +++ b/src/app/(admin)/admin/settings/page.tsx @@ -49,8 +49,9 @@ export default async function AdminSettingsPage() { {readiness ? ( ) : null} diff --git a/src/app/app/access/page.tsx b/src/app/app/access/page.tsx index 0021ad9..b36a6a8 100644 --- a/src/app/app/access/page.tsx +++ b/src/app/app/access/page.tsx @@ -2,7 +2,18 @@ import Link from "next/link"; import { Card } from "@/components/ui/card"; -function resolveCopy(status: string | undefined, reason: string | undefined) { +type AccessCopy = { + eyebrow: string; + title: string; + body: string; + reason?: string; + actionHref: string; + actionLabel: string; + /** Overrides the default "what this means" bullets (which describe suspension). */ + points?: string[]; +}; + +function resolveCopy(status: string | undefined, reason: string | undefined): AccessCopy { if (status === "superadmin-setup") { return { eyebrow: "Platform owner setup required", @@ -22,8 +33,18 @@ function resolveCopy(status: string | undefined, reason: string | undefined) { body: "Superadmin access is private and requires both an allowlisted email address and a persisted platform role.", reason: "Use the normal admin or buyer workspace for this account.", - actionHref: "/", - actionLabel: "Return to homepage", + // Send tenant operators back to their own workspace, not the marketing + // homepage — this page is reached by an ordinary admin who simply hit a + // /superadmin URL. + actionHref: "/admin", + actionLabel: "Go to your dashboard", + // The default bullets describe a SUSPENDED company, which is wrong here: + // nothing is blocked for this user, they just aren't a platform owner. + points: [ + "Your company workspace is unaffected — admin, portal, and payment actions all still work.", + "The platform owner dashboard is reserved for EstateOS staff.", + "If you reached this by mistake, continue in your own admin dashboard.", + ], }; } @@ -80,9 +101,15 @@ export default async function AppAccessPage({ What this means diff --git a/src/components/shared/logo.tsx b/src/components/shared/logo.tsx index d6560e2..cef1da5 100644 --- a/src/components/shared/logo.tsx +++ b/src/components/shared/logo.tsx @@ -35,8 +35,13 @@ export function Logo({ monogram )} -
-
+ {/* min-w-0 lets this flex child shrink inside narrow shells (e.g. the + dashboard sidebar). A hard min-width here caused long company names + such as "Blueprint Urban Residences LTD" to overflow and get clipped + by the parent's overflow-hidden. The name wraps instead of truncating + so the full company name always stays readable. */} +
+
{name}
{showTagline && tagline ? ( diff --git a/src/components/shared/tenant-readiness-checklist.tsx b/src/components/shared/tenant-readiness-checklist.tsx index 3967cc8..bff506c 100644 --- a/src/components/shared/tenant-readiness-checklist.tsx +++ b/src/components/shared/tenant-readiness-checklist.tsx @@ -16,18 +16,35 @@ const statusClasses = { missing: "border-rose-200 bg-rose-50 text-rose-800", } as const; +/** + * Readiness items owned by EstateOS itself (platform Paystack keys, R2 storage, + * superadmin-only checks). A tenant admin can neither see nor action these — and + * their action links point at /superadmin/*, which tenants cannot open — so they + * are hidden from the tenant audience and shown only to superadmins. + */ +const PLATFORM_OWNERS = new Set(["Platform", "Superadmin"]); + +export function isTenantVisibleReadinessItem(item: TenantReadinessItem) { + return !PLATFORM_OWNERS.has(item.owner) && !item.actionLink.startsWith("/superadmin"); +} + export function TenantReadinessChecklist({ title = "Go-live readiness", description = "Resolve these items before onboarding real buyers and payments.", items, + audience = "superadmin", }: { title?: string; description?: string; items: TenantReadinessItem[]; + /** "tenant" hides platform/superadmin-owned items. Defaults to the full list. */ + audience?: "tenant" | "superadmin"; }) { - const missing = items.filter((item) => item.status === "missing").length; - const warnings = items.filter((item) => item.status === "warning").length; - const complete = items.filter((item) => item.status === "complete").length; + const visibleItems = + audience === "tenant" ? items.filter(isTenantVisibleReadinessItem) : items; + const missing = visibleItems.filter((item) => item.status === "missing").length; + const warnings = visibleItems.filter((item) => item.status === "warning").length; + const complete = visibleItems.filter((item) => item.status === "complete").length; return ( @@ -43,7 +60,7 @@ export function TenantReadinessChecklist({
- {items.map((item) => ( + {visibleItems.map((item) => (
diff --git a/src/modules/portal/payments-layout.test.ts b/src/modules/portal/payments-layout.test.ts index e3331f6..6b1638d 100644 --- a/src/modules/portal/payments-layout.test.ts +++ b/src/modules/portal/payments-layout.test.ts @@ -81,7 +81,10 @@ test("payment summary cards wrap long values instead of clipping", () => { assert.match(logoSource, /showTagline = true/); assert.match(logoSource, /showTagline && tagline/); assert.match(dashboardShellSource, /showTagline=\{false\}/); - assert.match(logoSource, /overflow-visible/); + // The name wrapper uses break-words (not a hard min-width) so long company + // names wrap instead of overflowing the sidebar card. + assert.match(logoSource, /break-words/); + assert.doesNotMatch(logoSource, /sm:min-w-\[11rem\]/); assert.doesNotMatch(logoSource, /tenant-logo-tagline[^\n]+whitespace-nowrap/); assert.match(globalsSource, /--duration-fast: 120ms/); assert.match(globalsSource, /--tenant-motion-duration: 160ms/);