Assist Solutions
Customer Portal
diff --git a/apps/portal/src/components/organisms/AppShell/navigation.ts b/apps/portal/src/components/organisms/AppShell/navigation.ts
index 9e022ab4..81890079 100644
--- a/apps/portal/src/components/organisms/AppShell/navigation.ts
+++ b/apps/portal/src/components/organisms/AppShell/navigation.ts
@@ -1,4 +1,3 @@
-import type { Subscription } from "@customer-portal/domain/subscriptions";
import {
HomeIcon,
CreditCardIcon,
@@ -40,8 +39,8 @@ export const baseNavigation: NavigationItem[] = [
},
{
name: "Subscriptions",
+ href: "/account/subscriptions",
icon: ServerIcon,
- children: [{ name: "All Subscriptions", href: "/account/subscriptions" }],
},
{ name: "Services", href: "/account/services", icon: Squares2X2Icon },
{
@@ -59,36 +58,3 @@ export const baseNavigation: NavigationItem[] = [
},
{ name: "Log out", href: "#", icon: ArrowRightStartOnRectangleIcon, isLogout: true },
];
-
-export function computeNavigation(activeSubscriptions?: Subscription[]): NavigationItem[] {
- const nav: NavigationItem[] = baseNavigation.map(item => ({
- ...item,
- children: item.children ? [...item.children] : undefined,
- }));
-
- const subIdx = nav.findIndex(n => n.name === "Subscriptions");
- const currentItem = nav[subIdx];
- if (subIdx >= 0 && currentItem) {
- const dynamicChildren = (activeSubscriptions || []).map(sub => ({
- name: truncate(sub.productName || `Subscription ${sub.id}`, 28),
- href: `/account/subscriptions/${sub.id}`,
- tooltip: sub.productName || `Subscription ${sub.id}`,
- }));
-
- nav[subIdx] = {
- name: currentItem.name,
- icon: currentItem.icon,
- href: currentItem.href,
- isLogout: currentItem.isLogout,
- section: currentItem.section,
- children: [{ name: "All Subscriptions", href: "/account/subscriptions" }, ...dynamicChildren],
- };
- }
-
- return nav;
-}
-
-export function truncate(text: string, max: number): string {
- if (text.length <= max) return text;
- return text.slice(0, Math.max(0, max - 1)) + "…";
-}
diff --git a/apps/portal/src/components/templates/PageLayout/PageLayout.tsx b/apps/portal/src/components/templates/PageLayout/PageLayout.tsx
index 4dbda639..c81ecb34 100644
--- a/apps/portal/src/components/templates/PageLayout/PageLayout.tsx
+++ b/apps/portal/src/components/templates/PageLayout/PageLayout.tsx
@@ -1,20 +1,16 @@
import type { ReactNode } from "react";
import Link from "next/link";
-import { ChevronRightIcon } from "@heroicons/react/24/outline";
+import { ArrowLeftIcon } from "@heroicons/react/24/outline";
import { Skeleton } from "@/components/atoms/loading-skeleton";
import { ErrorState } from "@/components/atoms/error-state";
-export interface BreadcrumbItem {
- label: string;
- href?: string | undefined;
-}
-
interface PageLayoutProps {
icon?: ReactNode | undefined;
title: string;
description?: string | undefined;
actions?: ReactNode | undefined;
- breadcrumbs?: BreadcrumbItem[] | undefined;
+ backLink?: { label: string; href: string } | undefined;
+ statusPill?: ReactNode | undefined;
loading?: boolean | undefined;
loadingFallback?: ReactNode | undefined;
error?: Error | string | null | undefined;
@@ -27,7 +23,8 @@ export function PageLayout({
title,
description,
actions,
- breadcrumbs,
+ backLink,
+ statusPill,
loading = false,
loadingFallback,
error = null,
@@ -35,42 +32,24 @@ export function PageLayout({
children,
}: PageLayoutProps) {
return (
-
-
- {/* Breadcrumbs - scrollable on mobile */}
- {breadcrumbs && breadcrumbs.length > 0 && (
-
-
- {breadcrumbs.map((item, index) => (
-
- {index > 0 && (
-
- )}
- {item.href ? (
-
- {item.label}
-
- ) : (
-
- {item.label}
-
- )}
-
- ))}
-
-
- )}
+
+ {/* Header band with subtle background */}
+
+
+ {/* Back link */}
+ {backLink && (
+
+
+
+ {backLink.label}
+
+
+ )}
- {/* Header */}
-
-
- {/* Title row */}
+
{icon && (
@@ -78,9 +57,12 @@ export function PageLayout({
)}
-
- {title}
-
+
+
+ {title}
+
+ {statusPill}
+
{description && (
{description}
@@ -88,25 +70,27 @@ export function PageLayout({
)}
-
- {/* Actions - full width on mobile, stacks buttons */}
{actions && (
-
+
- {/* Content with loading and error states */}
-
- {renderPageContent({
- loading,
- error: error ?? undefined,
- children,
- onRetry,
- loadingFallback,
- })}
+ {/* Content */}
+
+
+
+ {renderPageContent({
+ loading,
+ error: error ?? undefined,
+ children,
+ onRetry,
+ loadingFallback,
+ })}
+
diff --git a/apps/portal/src/components/templates/PageLayout/index.ts b/apps/portal/src/components/templates/PageLayout/index.ts
index 17045872..c548d8c4 100644
--- a/apps/portal/src/components/templates/PageLayout/index.ts
+++ b/apps/portal/src/components/templates/PageLayout/index.ts
@@ -1,2 +1 @@
export { PageLayout } from "./PageLayout";
-export type { BreadcrumbItem } from "./PageLayout";
diff --git a/apps/portal/src/components/templates/index.ts b/apps/portal/src/components/templates/index.ts
index 42a1c1b1..451d8e6a 100644
--- a/apps/portal/src/components/templates/index.ts
+++ b/apps/portal/src/components/templates/index.ts
@@ -7,7 +7,6 @@ export { AuthLayout } from "./AuthLayout/AuthLayout";
export type { AuthLayoutProps } from "./AuthLayout/AuthLayout";
export { PageLayout } from "./PageLayout/PageLayout";
-export type { BreadcrumbItem } from "./PageLayout/PageLayout";
export { PublicShell } from "./PublicShell/PublicShell";
export type { PublicShellProps } from "./PublicShell/PublicShell";
diff --git a/apps/portal/src/features/account/views/ProfileContainer.tsx b/apps/portal/src/features/account/views/ProfileContainer.tsx
index 1579fa99..9ae614d4 100644
--- a/apps/portal/src/features/account/views/ProfileContainer.tsx
+++ b/apps/portal/src/features/account/views/ProfileContainer.tsx
@@ -124,25 +124,14 @@ export default function ProfileContainer() {
if (isLoading) {
return (
-
}
- title="Profile"
- description="Manage your account information"
- loading
- >
+
} title="Profile" loading>
);
}
return (
-
}
- title="Profile"
- description="Manage your account information"
- error={error}
- onRetry={reload}
- >
+
} title="Profile" error={error} onRetry={reload}>
{error && (
{error}
diff --git a/apps/portal/src/features/billing/views/InvoiceDetail.tsx b/apps/portal/src/features/billing/views/InvoiceDetail.tsx
index 40ee512b..85b86c69 100644
--- a/apps/portal/src/features/billing/views/InvoiceDetail.tsx
+++ b/apps/portal/src/features/billing/views/InvoiceDetail.tsx
@@ -1,7 +1,6 @@
"use client";
import { useState } from "react";
-import Link from "next/link";
import { useParams } from "next/navigation";
import { LoadingCard, Skeleton } from "@/components/atoms/loading-skeleton";
import { ErrorState } from "@/components/atoms/error-state";
@@ -18,11 +17,7 @@ import {
function InvoiceDetailSkeleton() {
return (
- }
- title="Invoice"
- description="Invoice details and actions"
- >
+ } title="Invoice">
@@ -86,18 +81,13 @@ export function InvoiceDetailContainer() {
}
title="Invoice"
- description="Invoice details and actions"
+ backLink={{ label: "Back to Invoices", href: "/account/billing/invoices" }}
>
-
-
- ← Back to invoices
-
-
);
}
@@ -106,12 +96,7 @@ export function InvoiceDetailContainer() {
}
title={`Invoice #${invoice.id}`}
- description="Invoice details and actions"
- breadcrumbs={[
- { label: "Billing", href: "/account/billing/invoices" },
- { label: "Invoices", href: "/account/billing/invoices" },
- { label: `#${invoice.id}` },
- ]}
+ backLink={{ label: "Back to Invoices", href: "/account/billing/invoices" }}
>
diff --git a/apps/portal/src/features/billing/views/InvoicesList.tsx b/apps/portal/src/features/billing/views/InvoicesList.tsx
index 05d9cdad..20bb713f 100644
--- a/apps/portal/src/features/billing/views/InvoicesList.tsx
+++ b/apps/portal/src/features/billing/views/InvoicesList.tsx
@@ -6,11 +6,7 @@ import { InvoicesList } from "@/features/billing/components/InvoiceList/InvoiceL
export function InvoicesListContainer() {
return (
-
}
- title="Invoices"
- description="Manage and view your billing invoices"
- >
+
} title="Invoices">
);
diff --git a/apps/portal/src/features/billing/views/PaymentMethods.tsx b/apps/portal/src/features/billing/views/PaymentMethods.tsx
index 2d966734..3febb59d 100644
--- a/apps/portal/src/features/billing/views/PaymentMethods.tsx
+++ b/apps/portal/src/features/billing/views/PaymentMethods.tsx
@@ -235,11 +235,7 @@ export function PaymentMethodsContainer() {
if (combinedError) {
return (
-
}
- title="Payment Methods"
- description="Manage your saved payment methods and billing information"
- >
+
} title="Payment Methods">
<>>
@@ -254,11 +250,7 @@ export function PaymentMethodsContainer() {
};
return (
-
}
- title="Payment Methods"
- description="Manage your saved payment methods and billing information"
- >
+
} title="Payment Methods">
;
return (
- }
- >
+ }>
+
@@ -167,7 +167,7 @@ function DashboardContent({
displayName: string;
}) {
return (
-
+
;
if (error) {
return (
-
+
-
-
Order Progress
+ } title="Order Progress" tone="info">
{statusDescriptor && (
)}
-
+
}
title={pageTitle}
- description={pageDescription}
- breadcrumbs={[{ label: "Orders", href: "/account/orders" }, { label: breadcrumbLabel }]}
+ backLink={{ label: "Back to Orders", href: "/account/orders" }}
>
{error &&
{error}
}
{isNewOrder &&
}
diff --git a/apps/portal/src/features/orders/views/OrdersList.tsx b/apps/portal/src/features/orders/views/OrdersList.tsx
index 5edb9e39..bbf97b14 100644
--- a/apps/portal/src/features/orders/views/OrdersList.tsx
+++ b/apps/portal/src/features/orders/views/OrdersList.tsx
@@ -224,11 +224,7 @@ export function OrdersListContainer() {
const summaryStatsItems = useMemo(() => buildOrderSummaryStats(stats), [stats]);
return (
-
}
- title="My Orders"
- description="View and track all your orders"
- >
+
} title="My Orders">
diff --git a/apps/portal/src/features/services/components/internet/configure/InternetConfigureContainer.tsx b/apps/portal/src/features/services/components/internet/configure/InternetConfigureContainer.tsx
index d86556a4..c548510e 100644
--- a/apps/portal/src/features/services/components/internet/configure/InternetConfigureContainer.tsx
+++ b/apps/portal/src/features/services/components/internet/configure/InternetConfigureContainer.tsx
@@ -198,7 +198,7 @@ export function InternetConfigureContainer({
}
title="Configure Internet Service"
- description="Set up your internet service options"
+ backLink={{ label: "Back to Services", href: "/account/services" }}
>
Plan not found
@@ -228,7 +228,7 @@ export function InternetConfigureContainer({
}
title="Configure Internet Service"
- description="Set up your internet service options"
+ backLink={{ label: "Back to Services", href: "/account/services" }}
>
diff --git a/apps/portal/src/features/services/components/internet/configure/components/ConfigureLoadingSkeleton.tsx b/apps/portal/src/features/services/components/internet/configure/components/ConfigureLoadingSkeleton.tsx
index 0d2c5750..9c59464e 100644
--- a/apps/portal/src/features/services/components/internet/configure/components/ConfigureLoadingSkeleton.tsx
+++ b/apps/portal/src/features/services/components/internet/configure/components/ConfigureLoadingSkeleton.tsx
@@ -5,11 +5,7 @@ import { ServerIcon } from "@heroicons/react/24/outline";
export function ConfigureLoadingSkeleton() {
return (
-
}
- title="Configure Internet Service"
- description="Set up your internet service options"
- >
+
} title="Configure Internet Service">
{/* Back to plans */}
diff --git a/apps/portal/src/features/services/components/sim/SimConfigureView.tsx b/apps/portal/src/features/services/components/sim/SimConfigureView.tsx
index cefd1f08..764a0193 100644
--- a/apps/portal/src/features/services/components/sim/SimConfigureView.tsx
+++ b/apps/portal/src/features/services/components/sim/SimConfigureView.tsx
@@ -154,7 +154,7 @@ export function SimConfigureView(props: Props) {
return (
}
>
diff --git a/apps/portal/src/features/services/components/sim/configure/LoadingSkeleton.tsx b/apps/portal/src/features/services/components/sim/configure/LoadingSkeleton.tsx
index d49355c7..93f32b8f 100644
--- a/apps/portal/src/features/services/components/sim/configure/LoadingSkeleton.tsx
+++ b/apps/portal/src/features/services/components/sim/configure/LoadingSkeleton.tsx
@@ -5,11 +5,7 @@ import { DevicePhoneMobileIcon } from "@heroicons/react/24/outline";
export function LoadingSkeleton() {
return (
-
}
- >
+
}>
{/* Header card skeleton */}
diff --git a/apps/portal/src/features/services/components/sim/configure/PlanNotFound.tsx b/apps/portal/src/features/services/components/sim/configure/PlanNotFound.tsx
index e5473d0c..da6108d7 100644
--- a/apps/portal/src/features/services/components/sim/configure/PlanNotFound.tsx
+++ b/apps/portal/src/features/services/components/sim/configure/PlanNotFound.tsx
@@ -8,11 +8,7 @@ export function PlanNotFound() {
const servicesBasePath = useServicesBasePath();
return (
-
}
- >
+
}>
Plan Not Found
diff --git a/apps/portal/src/features/services/views/AccountServicesOverview.tsx b/apps/portal/src/features/services/views/AccountServicesOverview.tsx
index 5cb89dd4..523db599 100644
--- a/apps/portal/src/features/services/views/AccountServicesOverview.tsx
+++ b/apps/portal/src/features/services/views/AccountServicesOverview.tsx
@@ -11,11 +11,7 @@ import { ServicesOverviewContent } from "@/features/services/components/common/S
*/
export function AccountServicesOverview() {
return (
-
}
- title="Services"
- description="Browse and order connectivity services"
- >
+
} title="Services">
);
diff --git a/apps/portal/src/features/subscriptions/components/CancellationFlow/CancellationFlow.tsx b/apps/portal/src/features/subscriptions/components/CancellationFlow/CancellationFlow.tsx
index dc5a8cf8..94911f33 100644
--- a/apps/portal/src/features/subscriptions/components/CancellationFlow/CancellationFlow.tsx
+++ b/apps/portal/src/features/subscriptions/components/CancellationFlow/CancellationFlow.tsx
@@ -1,11 +1,10 @@
"use client";
-import Link from "next/link";
import { useState, type ReactNode } from "react";
-import { PageLayout, type BreadcrumbItem } from "@/components/templates/PageLayout";
+import { PageLayout } from "@/components/templates/PageLayout";
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
import { Button } from "@/components/atoms";
-import { ArrowLeftIcon, CheckIcon, ExclamationTriangleIcon } from "@heroicons/react/24/outline";
+import { CheckIcon, ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ============================================================================
// Types
@@ -23,10 +22,6 @@ export interface CancellationFlowProps {
icon: ReactNode;
/** Page title */
title: string;
- /** Page description / subtitle */
- description: string;
- /** Breadcrumb items */
- breadcrumbs: BreadcrumbItem[];
/** Back link URL */
backHref: string;
/** Back link label */
@@ -511,8 +506,6 @@ function StepRouter(props: StepRouterProps) {
export function CancellationFlow({
icon,
title,
- description,
- breadcrumbs,
backHref,
backLabel,
availableMonths,
@@ -577,20 +570,12 @@ export function CancellationFlow({
-
-
- {backLabel}
-
{warningBanner}
diff --git a/apps/portal/src/features/subscriptions/components/SubscriptionGridCard.tsx b/apps/portal/src/features/subscriptions/components/SubscriptionGridCard.tsx
index 894526ec..8e6c429c 100644
--- a/apps/portal/src/features/subscriptions/components/SubscriptionGridCard.tsx
+++ b/apps/portal/src/features/subscriptions/components/SubscriptionGridCard.tsx
@@ -34,12 +34,14 @@ export function SubscriptionGridCard({ subscription, className }: SubscriptionGr
const { formatCurrency } = useFormatCurrency();
const statusIndicator = mapSubscriptionStatus(subscription.status);
const cycleLabel = getBillingCycleLabel(subscription.cycle);
+ const isInactive = ["Completed", "Cancelled", "Terminated"].includes(subscription.status);
return (
+
diff --git a/apps/portal/src/features/subscriptions/views/CancelSubscription.tsx b/apps/portal/src/features/subscriptions/views/CancelSubscription.tsx
index ff147b94..843ec2fe 100644
--- a/apps/portal/src/features/subscriptions/views/CancelSubscription.tsx
+++ b/apps/portal/src/features/subscriptions/views/CancelSubscription.tsx
@@ -43,12 +43,7 @@ function CancellationPendingView({
@@ -236,12 +231,6 @@ function CancellationFlowView({
}
title="Cancel Subscription"
- description="Loading cancellation information..."
- breadcrumbs={[{ label: "Subscriptions", href: SUBSCRIPTIONS_HREF }, { label: "Cancel" }]}
+ backLink={{ label: "Back to Subscriptions", href: SUBSCRIPTIONS_HREF }}
loading={state.loading}
error={state.error}
>
diff --git a/apps/portal/src/features/subscriptions/views/SimCallHistory.tsx b/apps/portal/src/features/subscriptions/views/SimCallHistory.tsx
index 6b9c4227..e2564ce2 100644
--- a/apps/portal/src/features/subscriptions/views/SimCallHistory.tsx
+++ b/apps/portal/src/features/subscriptions/views/SimCallHistory.tsx
@@ -128,7 +128,7 @@ export function SimCallHistoryContainer(): React.ReactElement {
}
title="Call & SMS History"
- description="View your call and SMS records"
+ backLink={{ label: "Back to Subscription", href: `/account/subscriptions/${subscriptionId}` }}
>
diff --git a/apps/portal/src/features/subscriptions/views/SimChangePlan.tsx b/apps/portal/src/features/subscriptions/views/SimChangePlan.tsx
index aeaada0d..58f712c3 100644
--- a/apps/portal/src/features/subscriptions/views/SimChangePlan.tsx
+++ b/apps/portal/src/features/subscriptions/views/SimChangePlan.tsx
@@ -238,7 +238,7 @@ export function SimChangePlanContainer() {
}
title="Change Plan"
- description="Switch to a different data plan"
+ backLink={{ label: "Back to Subscription", href: `/account/subscriptions/${subscriptionId}` }}
>
diff --git a/apps/portal/src/features/subscriptions/views/SimReissue.tsx b/apps/portal/src/features/subscriptions/views/SimReissue.tsx
index 72530d13..0e4ee321 100644
--- a/apps/portal/src/features/subscriptions/views/SimReissue.tsx
+++ b/apps/portal/src/features/subscriptions/views/SimReissue.tsx
@@ -331,7 +331,7 @@ export function SimReissueContainer() {
}
title="Reissue SIM"
- description="Request a replacement SIM card"
+ backLink={{ label: "Back to Subscription", href: `/account/subscriptions/${subscriptionId}` }}
>
diff --git a/apps/portal/src/features/subscriptions/views/SimTopUp.tsx b/apps/portal/src/features/subscriptions/views/SimTopUp.tsx
index 732abf84..84023015 100644
--- a/apps/portal/src/features/subscriptions/views/SimTopUp.tsx
+++ b/apps/portal/src/features/subscriptions/views/SimTopUp.tsx
@@ -100,7 +100,7 @@ export function SimTopUpContainer() {
}
title="Top Up Data"
- description="Add data to your SIM"
+ backLink={{ label: "Back to Subscription", href: `/account/subscriptions/${subscriptionId}` }}
>
diff --git a/apps/portal/src/features/subscriptions/views/SubscriptionDetail.tsx b/apps/portal/src/features/subscriptions/views/SubscriptionDetail.tsx
index 956f44e4..a21c01df 100644
--- a/apps/portal/src/features/subscriptions/views/SubscriptionDetail.tsx
+++ b/apps/portal/src/features/subscriptions/views/SubscriptionDetail.tsx
@@ -13,6 +13,7 @@ import {
import { useSubscription } from "@/features/subscriptions/hooks";
import { Formatting } from "@customer-portal/domain/toolkit";
import { PageLayout } from "@/components/templates/PageLayout";
+import { SectionCard } from "@/components/molecules/SectionCard";
import { StatusPill } from "@/components/atoms/status-pill";
import { SubscriptionDetailStatsSkeleton } from "@/components/atoms/loading-skeleton";
import { formatIsoDate, cn } from "@/shared/utils";
@@ -197,23 +198,24 @@ function SubscriptionDetailContent({
{isSim &&
}
{activeTab === "sim" && isSim &&
}
{activeTab === "overview" && (
-
-
-
-
-
Billing
-
+
}
+ title="Billing Information"
+ subtitle="Payment and invoices"
+ tone="primary"
+ actions={
- View all invoices →
+ View Invoices
-
-
+ }
+ >
+
Invoices and payment history are available on the billing page.
-
+
)}
);
@@ -231,11 +233,7 @@ export function SubscriptionDetailContainer() {
}
title="Subscription"
- description="Loading subscription details..."
- breadcrumbs={[
- { label: "Subscriptions", href: "/account/subscriptions" },
- { label: "Subscription" },
- ]}
+ backLink={{ label: "Back to Subscriptions", href: "/account/subscriptions" }}
>
@@ -255,10 +253,7 @@ export function SubscriptionDetailContainer() {
icon={
}
title={subscription?.productName ?? "Subscription"}
actions={headerActions}
- breadcrumbs={[
- { label: "Subscriptions", href: "/account/subscriptions" },
- { label: subscription?.productName ?? "Subscription" },
- ]}
+ backLink={{ label: "Back to Subscriptions", href: "/account/subscriptions" }}
error={getPageError(error)}
>
{subscription ? (
diff --git a/apps/portal/src/features/subscriptions/views/SubscriptionsList.tsx b/apps/portal/src/features/subscriptions/views/SubscriptionsList.tsx
index 25302b68..e7f285cf 100644
--- a/apps/portal/src/features/subscriptions/views/SubscriptionsList.tsx
+++ b/apps/portal/src/features/subscriptions/views/SubscriptionsList.tsx
@@ -146,11 +146,7 @@ export function SubscriptionsListContainer() {
if (showLoading || error) {
return (
-
}
- title="Subscriptions"
- description="Manage your active subscriptions"
- >
+
} title="Subscriptions">
@@ -165,7 +161,6 @@ export function SubscriptionsListContainer() {
}
title="Subscriptions"
- description="Manage your active subscriptions"
actions={
Browse Services
@@ -175,7 +170,7 @@ export function SubscriptionsListContainer() {
{stats && }
-
+
}
title="Create Support Case"
- description="Get help from our support team"
- breadcrumbs={[{ label: "Support", href: "/account/support" }, { label: "Create Case" }]}
+ backLink={{ label: "Back to Cases", href: "/account/support" }}
>
diff --git a/apps/portal/src/features/support/views/SupportCaseDetailView.tsx b/apps/portal/src/features/support/views/SupportCaseDetailView.tsx
index 2dc309b7..28a2f92b 100644
--- a/apps/portal/src/features/support/views/SupportCaseDetailView.tsx
+++ b/apps/portal/src/features/support/views/SupportCaseDetailView.tsx
@@ -291,15 +291,10 @@ export function SupportCaseDetailView({ caseId }: SupportCaseDetailViewProps): R
}
title={state.supportCase ? `Case #${state.supportCase.caseNumber}` : "Loading..."}
- description={state.supportCase?.subject}
loading={state.showLoading}
error={state.pageError}
onRetry={() => void state.refetch()}
- breadcrumbs={[
- { label: "Support", href: SUPPORT_HREF },
- { label: "Cases", href: SUPPORT_HREF },
- { label: state.supportCase ? `#${state.supportCase.caseNumber}` : "..." },
- ]}
+ backLink={{ label: "Back to Cases", href: SUPPORT_HREF }}
actions={
}
title="Case Not Found"
- breadcrumbs={[
- { label: "Support", href: SUPPORT_HREF },
- { label: "Cases", href: SUPPORT_HREF },
- { label: "Not Found" },
- ]}
+ backLink={{ label: "Back to Cases", href: SUPPORT_HREF }}
>
The support case you're looking for could not be found or you don't have
diff --git a/apps/portal/src/features/support/views/SupportCasesView.tsx b/apps/portal/src/features/support/views/SupportCasesView.tsx
index e96a2cfd..5cf25092 100644
--- a/apps/portal/src/features/support/views/SupportCasesView.tsx
+++ b/apps/portal/src/features/support/views/SupportCasesView.tsx
@@ -233,11 +233,9 @@ export function SupportCasesView() {
}
title="Support Cases"
- description="Track and manage your support requests"
loading={showLoading}
error={error}
onRetry={() => void refetch()}
- breadcrumbs={[{ label: "Support", href: "/account/support" }, { label: "Cases" }]}
actions={
}>
New Case
diff --git a/apps/portal/src/features/support/views/SupportHomeView.tsx b/apps/portal/src/features/support/views/SupportHomeView.tsx
index f91e1f34..90acbc61 100644
--- a/apps/portal/src/features/support/views/SupportHomeView.tsx
+++ b/apps/portal/src/features/support/views/SupportHomeView.tsx
@@ -109,7 +109,6 @@ export function SupportHomeView() {
}
title="Support Center"
- description="Get help with your account and services"
loading={showLoading}
error={error}
onRetry={() => void refetch()}
diff --git a/apps/portal/src/features/verification/views/ResidenceCardVerificationSettingsView.tsx b/apps/portal/src/features/verification/views/ResidenceCardVerificationSettingsView.tsx
index 92028e50..0ddd486d 100644
--- a/apps/portal/src/features/verification/views/ResidenceCardVerificationSettingsView.tsx
+++ b/apps/portal/src/features/verification/views/ResidenceCardVerificationSettingsView.tsx
@@ -373,9 +373,8 @@ export function ResidenceCardVerificationSettingsView(): React.ReactElement {
return (
}
- breadcrumbs={[{ label: "Settings", href: "/account/settings" }, { label: "ID Verification" }]}
+ backLink={{ label: "Back to Settings", href: "/account/settings" }}
>
` component variants (not inline-styled).
+
+**Affected pages:** SubscriptionDetail, OrderDetail, InvoiceDetail, SupportCaseDetailView.
+
+### 4. PageLayout Template Changes
+
+PageLayout becomes the single source of truth for page header styling. Changes:
+
+- **Header area:** Always renders with `bg-muted/40 border-b border-border/40` background. No more plain/transparent headers.
+- **Remove description:** Drop the `description` prop usage — title is enough, description was redundant with sidebar context.
+- **New `backLink` prop:** `{ label: string; href: string }` — renders `← Back to {parent}` above the header on detail pages. List pages don't pass this.
+- **Remove breadcrumbs:** Drop breadcrumb rendering entirely — replaced by `backLink` on detail pages, sidebar on list pages.
+- **Header layout:** `flex items-center justify-between` — icon + title on left, actions on right. Status pills sit next to the title.
+- **Consistent spacing:** Header padding `px-6 py-4`, content area spacing `space-y-6`.
+
+This means every account page automatically gets the same header treatment by using PageLayout. No per-page header styling needed.
+
+### 5. Section Card Pattern
+
+Every content section in detail pages uses this structure:
+
+```
+bg-card rounded-xl border border-border shadow-[var(--cp-shadow-1)] overflow-hidden
+ Header (bg-muted/40 px-6 py-4 border-b border-border/40)
+ flex items-center justify-between
+ Left: flex items-center gap-3
+ Icon circle (h-9 w-9 rounded-lg bg-{tone}/10 text-{tone})
+ Title: text-sm font-semibold text-foreground
+ Subtitle: text-xs text-muted-foreground
+ Right: action button(s) or link
+ Content (px-6 py-5)
+```
+
+Each section gets a semantic tone for its icon circle:
+
+- Billing: `primary`
+- SIM Management: `info`
+- Order Items: `success`
+- Support: `warning`
+
+### 6. Subscriptions-Specific Fixes
+
+- **Active/inactive dimming:** Active subscriptions render normally. Completed/Cancelled/Terminated get `opacity-60` on the grid card + muted status indicator.
+- **Cancel button:** Move from inline-styled to `` via PageLayout actions.
+- **Domain imports:** Fix `@customer-portal/domain/subscriptions` module resolution (run `pnpm domain:build`).
+- **Grid card borders:** Standardize from `border-border/60` to `border-border`.
+
+### 7. Cross-Page Consistency
+
+**Typography:**
+
+- Page titles: PageLayout handles it (`text-xl sm:text-2xl md:text-3xl font-bold`). Remove custom title sizes in views.
+- Section headings: `text-sm font-semibold text-foreground` inside section cards.
+- Stat values: `font-heading` for numbers.
+
+**Buttons — always use `` component:**
+
+- Primary actions: `variant="default"` (Browse Services, New Case)
+- Secondary: `variant="outline"` (Back, Retry, View All)
+- Destructive: `variant="destructive"` (Cancel Service)
+- Links: `variant="link"` or `ghost` for in-section navigation
+
+**Status display:**
+
+- Always `` — no inline badge classes
+- `size="sm"` in list views, `size="md"` in detail headers
+
+**Borders:**
+
+- Outer cards: `border border-border`
+- Internal dividers only: `border-b border-border/40`
+
+**Spacing:**
+
+- Card padding: `p-4` for list item cards, `p-5` for container sections
+- Gap between items: `gap-3`
+- Section spacing: `space-y-6`
+
+### 8. Navigation Changes
+
+- **List pages:** No back link — sidebar handles navigation.
+- **Detail pages:** `← Back to {parent}` link above title.
+- **Breadcrumbs:** Removed — redundant with page title.
+- **Sidebar subscriptions:** Direct link, no expandable children.
+
+## Out of Scope
+
+- Landing page color token migration (separate effort)
+- New component creation beyond what's needed for the patterns above
+- Mobile-specific layout changes
diff --git a/docs/plans/2026-03-05-portal-ui-cleanup-plan.md b/docs/plans/2026-03-05-portal-ui-cleanup-plan.md
new file mode 100644
index 00000000..f728d5cc
--- /dev/null
+++ b/docs/plans/2026-03-05-portal-ui-cleanup-plan.md
@@ -0,0 +1,774 @@
+# Portal UI Cleanup Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Unify all portal account pages with consistent styling, fix sidebar brand alignment, simplify navigation, and standardize list/detail page patterns.
+
+**Architecture:** Update PageLayout template to be the single source of truth for page headers (bg-muted/40 background, backLink support, no breadcrumbs). Create a reusable SectionCard molecule for detail pages. Update all views to use these patterns consistently.
+
+**Tech Stack:** Next.js 15, React 19, Tailwind CSS v4, CSS custom properties (OKLCH), shadcn/ui patterns
+
+---
+
+### Task 1: Fix Sidebar Color & Logo
+
+**Files:**
+
+- Modify: `apps/portal/src/app/globals.css` (lines 83-88 for sidebar tokens, lines 197-199 for dark mode)
+- Modify: `apps/portal/src/components/atoms/logo.tsx` (lines 27-33 for SVG colors)
+- Modify: `apps/portal/src/components/organisms/AppShell/Sidebar.tsx` (lines 66-76 for logo container)
+
+**Step 1: Update sidebar CSS tokens**
+
+In `globals.css`, change the sidebar variables in `:root`:
+
+```css
+/* Before */
+--sidebar: oklch(0.18 0.03 250);
+--sidebar-border: oklch(0.25 0.04 250);
+
+/* After — deeper primary blue hue (234.4), more saturated */
+--sidebar: oklch(0.2 0.06 234.4);
+--sidebar-border: oklch(0.27 0.05 234.4);
+```
+
+In `.dark`, update sidebar:
+
+```css
+/* Before */
+--sidebar: oklch(0.13 0.025 250);
+--sidebar-border: oklch(0.22 0.03 250);
+
+/* After */
+--sidebar: oklch(0.14 0.04 234.4);
+--sidebar-border: oklch(0.22 0.04 234.4);
+```
+
+**Step 2: Update logo SVG fallback to white**
+
+In `logo.tsx`, change the SVG path fills:
+
+```tsx
+// Before
+
+
+
+
+
+// After
+
+
+
+
+```
+
+**Step 3: Simplify logo container in Sidebar**
+
+In `Sidebar.tsx`, remove the `bg-white/10` container around the logo:
+
+```tsx
+// Before (lines 66-76)
+
+
+
+
+
+
+
Assist Solutions
+
Customer Portal
+
+
+
+
+// After — logo sits directly, no glass container
+
+
+
+
+
Assist Solutions
+
Customer Portal
+
+
+
+```
+
+**Step 4: Verify visually**
+
+Run: `pnpm --filter @customer-portal/portal dev` (with user permission)
+Check: Sidebar color is deeper blue matching brand, logo is clearly visible white, text remains readable.
+
+**Step 5: Commit**
+
+```
+style: align sidebar color with brand blue and use white logo
+```
+
+---
+
+### Task 2: Simplify Subscriptions Navigation
+
+**Files:**
+
+- Modify: `apps/portal/src/components/organisms/AppShell/navigation.ts` (lines 29-94)
+- Modify: `apps/portal/src/components/organisms/AppShell/AppShell.tsx` (remove subscription data dependency)
+
+**Step 1: Change Subscriptions to a direct link**
+
+In `navigation.ts`, change the Subscriptions entry from expandable to direct:
+
+```ts
+// Before (lines 41-44)
+{
+ name: "Subscriptions",
+ icon: ServerIcon,
+ children: [{ name: "All Subscriptions", href: "/account/subscriptions" }],
+},
+
+// After
+{
+ name: "Subscriptions",
+ href: "/account/subscriptions",
+ icon: ServerIcon,
+},
+```
+
+**Step 2: Remove `computeNavigation` function**
+
+Delete the `computeNavigation` function (lines 63-89) and the `truncate` helper (lines 91-94). Export only `baseNavigation`.
+
+**Step 3: Remove the `Subscription` import**
+
+Remove line 1: `import type { Subscription } from "@customer-portal/domain/subscriptions";`
+
+This also fixes the TypeScript error `Cannot find module '@customer-portal/domain/subscriptions'`.
+
+**Step 4: Update AppShell to use `baseNavigation` directly**
+
+In `AppShell.tsx`, find where `computeNavigation` is called and replace with `baseNavigation`:
+
+```ts
+// Before
+import { computeNavigation } from "./navigation";
+// ... later
+const navigation = computeNavigation(activeSubscriptions);
+
+// After
+import { baseNavigation } from "./navigation";
+// ... later — use baseNavigation directly, remove activeSubscriptions fetch
+```
+
+Remove any hook/fetch for active subscriptions that was only used for sidebar navigation.
+
+**Step 5: Verify**
+
+Run: `pnpm type-check`
+Expected: No errors related to navigation.ts or AppShell.tsx
+
+**Step 6: Commit**
+
+```
+refactor: simplify subscriptions to direct sidebar link
+```
+
+---
+
+### Task 3: Update PageLayout Template
+
+**Files:**
+
+- Modify: `apps/portal/src/components/templates/PageLayout/PageLayout.tsx`
+
+**Step 1: Update PageLayout interface**
+
+```tsx
+// Remove BreadcrumbItem export and breadcrumbs prop
+// Add backLink prop
+
+interface PageLayoutProps {
+ icon?: ReactNode | undefined;
+ title: string;
+ description?: string | undefined; // keep prop but deprecate usage
+ actions?: ReactNode | undefined;
+ backLink?: { label: string; href: string } | undefined;
+ statusPill?: ReactNode | undefined; // new: renders next to title
+ loading?: boolean | undefined;
+ loadingFallback?: ReactNode | undefined;
+ error?: Error | string | null | undefined;
+ onRetry?: (() => void) | undefined;
+ children: ReactNode;
+}
+```
+
+**Step 2: Rewrite the header section**
+
+Replace the entire header block (lines 40-99) with:
+
+```tsx
+{
+ /* Back link — detail pages only */
+}
+{
+ backLink && (
+
+
+
+ {backLink.label}
+
+
+ );
+}
+
+{
+ /* Header with muted background */
+}
+
+
+
+ {/* Left: icon + title + status */}
+
+ {icon &&
{icon}
}
+
+
+
+ {title}
+
+ {statusPill}
+
+ {description && (
+
+ {description}
+
+ )}
+
+
+
+ {/* Right: actions */}
+ {actions &&
{actions}
}
+
+
+
;
+```
+
+**Step 3: Update outer wrapper structure**
+
+The header now has its own bg, so it should sit outside the content container. Restructure:
+
+```tsx
+export function PageLayout({ ... }: PageLayoutProps) {
+ return (
+
+ {/* Back link */}
+ {backLink && ( ... )}
+
+ {/* Header with muted background */}
+
+
+ ...header content...
+
+
+
+ {/* Content */}
+
+
+
+ {renderPageContent({ loading, error: error ?? undefined, children, onRetry, loadingFallback })}
+
+
+
+
+ );
+}
+```
+
+**Step 4: Add ArrowLeftIcon import**
+
+```tsx
+import { ArrowLeftIcon } from "@heroicons/react/24/outline";
+```
+
+**Step 5: Remove old breadcrumb code and BreadcrumbItem export**
+
+Delete the `BreadcrumbItem` interface and all breadcrumb rendering code. Keep the `ChevronRightIcon` import removal too.
+
+**Step 6: Verify**
+
+Run: `pnpm type-check`
+Expected: May show errors in files still passing `breadcrumbs` prop — those get fixed in later tasks.
+
+**Step 7: Commit**
+
+```
+refactor: update PageLayout with muted header bg and backLink support
+```
+
+---
+
+### Task 4: Create SectionCard Molecule
+
+**Files:**
+
+- Create: `apps/portal/src/components/molecules/SectionCard/SectionCard.tsx`
+- Create: `apps/portal/src/components/molecules/SectionCard/index.ts`
+- Modify: `apps/portal/src/components/molecules/index.ts` (add export)
+
+**Step 1: Create SectionCard component**
+
+```tsx
+// apps/portal/src/components/molecules/SectionCard/SectionCard.tsx
+import type { ReactNode } from "react";
+import { cn } from "@/shared/utils";
+
+type SectionTone = "primary" | "success" | "info" | "warning" | "danger" | "neutral";
+
+const toneStyles: Record = {
+ primary: "bg-primary/10 text-primary",
+ success: "bg-success/10 text-success",
+ info: "bg-info/10 text-info",
+ warning: "bg-warning/10 text-warning",
+ danger: "bg-danger/10 text-danger",
+ neutral: "bg-neutral/10 text-neutral",
+};
+
+interface SectionCardProps {
+ icon: ReactNode;
+ title: string;
+ subtitle?: string | undefined;
+ tone?: SectionTone;
+ actions?: ReactNode | undefined;
+ children: ReactNode;
+ className?: string | undefined;
+}
+
+export function SectionCard({
+ icon,
+ title,
+ subtitle,
+ tone = "primary",
+ actions,
+ children,
+ className,
+}: SectionCardProps) {
+ return (
+
+ {/* Header */}
+
+
+
+
+ {icon}
+
+
+
{title}
+ {subtitle &&
{subtitle}
}
+
+
+ {actions &&
{actions}
}
+
+
+
+ {/* Content */}
+
{children}
+
+ );
+}
+```
+
+**Step 2: Create barrel export**
+
+```tsx
+// apps/portal/src/components/molecules/SectionCard/index.ts
+export { SectionCard } from "./SectionCard";
+```
+
+**Step 3: Add to molecules index**
+
+In `apps/portal/src/components/molecules/index.ts`, add:
+
+```tsx
+export { SectionCard } from "./SectionCard";
+```
+
+**Step 4: Verify**
+
+Run: `pnpm type-check`
+Expected: No errors.
+
+**Step 5: Commit**
+
+```
+feat: add SectionCard molecule for unified detail page sections
+```
+
+---
+
+### Task 5: Update Subscriptions List View
+
+**Files:**
+
+- Modify: `apps/portal/src/features/subscriptions/views/SubscriptionsList.tsx`
+- Modify: `apps/portal/src/features/subscriptions/components/SubscriptionGridCard.tsx`
+
+**Step 1: Remove description from PageLayout call**
+
+```tsx
+// Before
+ } title="Subscriptions" description="Manage your active subscriptions">
+
+// After
+ } title="Subscriptions">
+```
+
+Do this for both the loading state and the main render.
+
+**Step 2: Standardize content card borders**
+
+```tsx
+// Before
+
+
+// After
+
+```
+
+**Step 3: Add opacity dimming for inactive subscriptions in SubscriptionGridCard**
+
+In `SubscriptionGridCard.tsx`, wrap the card Link with conditional opacity:
+
+```tsx
+// Add to the Link className, conditionally:
+const isInactive = ["Completed", "Cancelled", "Terminated"].includes(subscription.status);
+
+// In the className of the Link:
+className={cn(
+ "group flex flex-col p-4 rounded-xl bg-card border border-border transition-all duration-200 hover:shadow-[var(--cp-shadow-2)] hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30",
+ isInactive && "opacity-60"
+)}
+```
+
+Also standardize card border from `border-border/60` to `border-border`.
+
+**Step 4: Verify**
+
+Run: `pnpm type-check`
+
+**Step 5: Commit**
+
+```
+style: clean up subscriptions list with unified patterns
+```
+
+---
+
+### Task 6: Update Subscription Detail View
+
+**Files:**
+
+- Modify: `apps/portal/src/features/subscriptions/views/SubscriptionDetail.tsx`
+
+**Step 1: Replace breadcrumbs with backLink**
+
+```tsx
+// Before
+breadcrumbs={[
+ { label: "Subscriptions", href: "/account/subscriptions" },
+ { label: subscription.productName },
+]}
+
+// After
+backLink={{ label: "Back to Subscriptions", href: "/account/subscriptions" }}
+```
+
+**Step 2: Move cancel button to PageLayout actions**
+
+Replace any inline-styled cancel button with:
+
+```tsx
+actions={
+ subscription.status === "Active" && isInternetService ? (
+
+ Cancel Service
+
+ ) : undefined
+}
+```
+
+Remove the old inline cancel button from the body.
+
+**Step 3: Use SectionCard for billing section**
+
+```tsx
+import { SectionCard } from "@/components/molecules/SectionCard";
+
+// Replace the billing card with:
+
}
+ title="Billing Information"
+ subtitle="Payment and invoices"
+ tone="primary"
+ actions={
+
+ View Invoices
+
+ }
+>
+ {/* billing content */}
+;
+```
+
+**Step 4: Remove description from PageLayout**
+
+Drop `description` prop from PageLayout usage.
+
+**Step 5: Verify**
+
+Run: `pnpm type-check`
+
+**Step 6: Commit**
+
+```
+style: update subscription detail with backLink and SectionCard
+```
+
+---
+
+### Task 7: Update Orders Views
+
+**Files:**
+
+- Modify: `apps/portal/src/features/orders/views/OrdersList.tsx`
+- Modify: `apps/portal/src/features/orders/views/OrderDetail.tsx`
+
+**Step 1: OrdersList — remove description, standardize card borders**
+
+Remove `description` prop from PageLayout. Change any `border-border/60` to `border-border`. Replace inline status badge classes with `StatusPill` component.
+
+**Step 2: OrderDetail — replace breadcrumbs with backLink**
+
+```tsx
+// Before
+breadcrumbs={[{ label: "Orders", href: "/account/orders" }, { label: `Order #${id}` }]}
+
+// After
+backLink={{ label: "Back to Orders", href: "/account/orders" }}
+```
+
+Remove `description` prop. Use `SectionCard` for order item sections.
+
+**Step 3: Remove any custom title sizes**
+
+If OrderDetail has its own `text-2xl` title styling, remove it — let PageLayout handle the title.
+
+**Step 4: Verify**
+
+Run: `pnpm type-check`
+
+**Step 5: Commit**
+
+```
+style: update order views with unified patterns
+```
+
+---
+
+### Task 8: Update Billing Views
+
+**Files:**
+
+- Modify: `apps/portal/src/features/billing/views/InvoicesList.tsx`
+- Modify: `apps/portal/src/features/billing/views/InvoiceDetail.tsx`
+- Modify: `apps/portal/src/features/billing/views/PaymentMethods.tsx`
+
+**Step 1: InvoicesList — remove description**
+
+Drop `description` prop from PageLayout.
+
+**Step 2: InvoiceDetail — replace breadcrumbs with backLink**
+
+```tsx
+backLink={{ label: "Back to Invoices", href: "/account/billing/invoices" }}
+```
+
+Remove `description`. Use `SectionCard` for invoice detail sections where appropriate.
+
+**Step 3: PaymentMethods — remove description**
+
+Drop `description` prop.
+
+**Step 4: Verify**
+
+Run: `pnpm type-check`
+
+**Step 5: Commit**
+
+```
+style: update billing views with unified patterns
+```
+
+---
+
+### Task 9: Update Support Views
+
+**Files:**
+
+- Modify: `apps/portal/src/features/support/views/SupportCasesView.tsx`
+- Modify: `apps/portal/src/features/support/views/SupportCaseDetailView.tsx`
+- Modify: `apps/portal/src/features/support/views/NewSupportCaseView.tsx`
+
+**Step 1: SupportCasesView — remove description, fix inline badges**
+
+Remove `description` and `breadcrumbs` props from PageLayout. Replace inline badge classes (`inline-flex text-xs px-2 py-0.5 rounded font-medium`) with `
` component.
+
+**Step 2: SupportCaseDetailView — replace breadcrumbs with backLink**
+
+```tsx
+backLink={{ label: "Back to Cases", href: "/account/support" }}
+```
+
+Remove `description`. Use `SectionCard` for conversation and meta sections.
+
+**Step 3: NewSupportCaseView — replace breadcrumbs with backLink**
+
+```tsx
+backLink={{ label: "Back to Cases", href: "/account/support" }}
+```
+
+Remove `description`.
+
+**Step 4: Verify**
+
+Run: `pnpm type-check`
+
+**Step 5: Commit**
+
+```
+style: update support views with unified patterns
+```
+
+---
+
+### Task 10: Update Remaining Views
+
+**Files:**
+
+- Modify: `apps/portal/src/features/dashboard/views/DashboardView.tsx`
+- Modify: `apps/portal/src/features/services/views/AccountServicesOverview.tsx`
+- Modify: `apps/portal/src/features/account/views/ProfileContainer.tsx`
+- Modify: `apps/portal/src/features/verification/views/ResidenceCardVerificationSettingsView.tsx`
+- Modify: `apps/portal/src/features/subscriptions/views/SimReissue.tsx`
+- Modify: `apps/portal/src/features/subscriptions/views/SimTopUp.tsx`
+- Modify: `apps/portal/src/features/subscriptions/views/SimChangePlan.tsx`
+- Modify: `apps/portal/src/features/subscriptions/views/SimCallHistory.tsx`
+- Modify: `apps/portal/src/features/subscriptions/views/CancelSubscription.tsx`
+- Modify: `apps/portal/src/features/subscriptions/components/CancellationFlow/CancellationFlow.tsx`
+- Modify: `apps/portal/src/features/checkout/components/AccountCheckoutContainer.tsx`
+- Modify: `apps/portal/src/features/services/components/sim/SimConfigureView.tsx`
+- Modify: `apps/portal/src/features/services/components/internet/configure/InternetConfigureContainer.tsx`
+
+**Step 1: Remove description from all remaining PageLayout usages**
+
+For every file listed above, remove the `description` prop from ``.
+
+**Step 2: Replace breadcrumbs with backLink where applicable**
+
+- `ResidenceCardVerificationSettingsView.tsx`: `backLink={{ label: "Back to Settings", href: "/account/settings" }}`
+- `CancelSubscription.tsx` / `CancellationFlow.tsx`: `backLink={{ label: "Back to Subscription", href: "/account/subscriptions/{id}" }}`
+- SIM views (Reissue, TopUp, ChangePlan, CallHistory): `backLink={{ label: "Back to Subscription", href: "/account/subscriptions/{id}" }}`
+
+**Step 3: Verify**
+
+Run: `pnpm type-check`
+Expected: No errors. All `breadcrumbs` prop usages should be gone.
+
+**Step 4: Commit**
+
+```
+style: remove descriptions and breadcrumbs from all remaining views
+```
+
+---
+
+### Task 11: Fix Domain Build & Clean Up
+
+**Files:**
+
+- Modify: `apps/portal/src/features/subscriptions/components/SubscriptionGridCard.tsx` (if import still broken)
+- Modify: `apps/portal/src/components/templates/PageLayout/PageLayout.tsx` (remove dead BreadcrumbItem type if not already)
+
+**Step 1: Build domain package**
+
+Run: `pnpm domain:build`
+Expected: Successful build, resolves `@customer-portal/domain/subscriptions` import errors.
+
+**Step 2: Remove BreadcrumbItem from PageLayout exports**
+
+If `BreadcrumbItem` is still exported, remove it. Check if any file imports it:
+
+Run: `grep -r "BreadcrumbItem" apps/portal/src/`
+
+Remove any remaining imports.
+
+**Step 3: Full type check**
+
+Run: `pnpm type-check`
+Expected: Clean — no errors.
+
+**Step 4: Lint check**
+
+Run: `pnpm lint`
+Expected: Clean or only pre-existing warnings.
+
+**Step 5: Commit**
+
+```
+chore: fix domain build and remove dead breadcrumb types
+```
+
+---
+
+### Task 12: Final Visual Verification
+
+**Step 1: Start dev server (with user permission)**
+
+Run: `pnpm --filter @customer-portal/portal dev`
+
+**Step 2: Verify each page visually**
+
+Check these pages match the design:
+
+- [ ] Sidebar: deeper blue, white logo, Subscriptions as direct link
+- [ ] Dashboard (`/account`): muted header bg, no description
+- [ ] Subscriptions list (`/account/subscriptions`): muted header, metrics row, unified content card, inactive subs dimmed
+- [ ] Subscription detail (`/account/subscriptions/{id}`): back link, header with actions, SectionCard for billing
+- [ ] Orders list (`/account/orders`): consistent with subscriptions list pattern
+- [ ] Order detail (`/account/orders/{id}`): back link, SectionCards
+- [ ] Invoices (`/account/billing/invoices`): consistent list pattern
+- [ ] Invoice detail: back link, consistent
+- [ ] Support cases (`/account/support`): StatusPill badges, no inline badges
+- [ ] Support detail: back link, consistent
+- [ ] Settings (`/account/settings`): muted header
+
+**Step 3: Fix any visual issues found**
+
+Address spacing, alignment, or color issues discovered during review.
+
+**Step 4: Final commit**
+
+```
+style: portal UI cleanup — unified page patterns and brand alignment
+```