# 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
<path d="M8 8 C12 4, 20 4, 24 8 L20 12 C18 10, 14 10, 12 12 Z" fill="#60A5FA" />
<path d="M24 8 C28 12, 28 20, 24 24 L20 20 C22 18, 22 14, 20 12 Z" fill="#60A5FA" />
<path d="M8 24 C12 28, 20 28, 24 24 L20 20 C18 22, 14 22, 12 20 Z" fill="#1E40AF" />
<path d="M8 24 C4 20, 4 12, 8 8 L12 12 C10 14, 10 18, 12 20 Z" fill="#1E40AF" />

// After
<path d="M8 8 C12 4, 20 4, 24 8 L20 12 C18 10, 14 10, 12 12 Z" fill="#ffffff" />
<path d="M24 8 C28 12, 28 20, 24 24 L20 20 C22 18, 22 14, 20 12 Z" fill="#ffffff" />
<path d="M8 24 C12 28, 20 28, 24 24 L20 20 C18 22, 14 22, 12 20 Z" fill="rgba(255,255,255,0.6)" />
<path d="M8 24 C4 20, 4 12, 8 8 L12 12 C10 14, 10 18, 12 20 Z" fill="rgba(255,255,255,0.6)" />
```

**Step 3: Simplify logo container in Sidebar**

In `Sidebar.tsx`, remove the `bg-white/10` container around the logo:

```tsx
// Before (lines 66-76)
<div className="flex items-center flex-shrink-0 h-16 px-5 border-b border-sidebar-border/50">
  <div className="flex items-center space-x-3">
    <div className="h-9 w-9 bg-white/10 backdrop-blur-sm rounded-lg border border-white/10 flex items-center justify-center">
      <Logo size={22} />
    </div>
    <div>
      <span className="text-sm font-bold text-white tracking-tight">Assist Solutions</span>
      <p className="text-[11px] text-white/50 font-medium">Customer Portal</p>
    </div>
  </div>
</div>

// After — logo sits directly, no glass container
<div className="flex items-center flex-shrink-0 h-16 px-5 border-b border-sidebar-border/50">
  <div className="flex items-center space-x-3">
    <Logo size={28} />
    <div>
      <span className="text-sm font-bold text-white tracking-tight">Assist Solutions</span>
      <p className="text-[11px] text-white/50 font-medium">Customer Portal</p>
    </div>
  </div>
</div>
```

**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 && (
    <div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-space-md)] sm:px-[var(--cp-space-lg)] md:px-8 pt-[var(--cp-space-md)]">
      <Link
        href={backLink.href}
        className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors duration-200"
      >
        <ArrowLeftIcon className="h-4 w-4" />
        {backLink.label}
      </Link>
    </div>
  );
}

{
  /* Header with muted background */
}
<div className="bg-muted/40 border-b border-border/40">
  <div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-space-md)] sm:px-[var(--cp-space-lg)] md:px-8 py-4">
    <div className="flex items-center justify-between gap-4">
      {/* Left: icon + title + status */}
      <div className="flex items-center gap-3 min-w-0">
        {icon && <div className="h-7 w-7 sm:h-8 sm:w-8 text-primary flex-shrink-0">{icon}</div>}
        <div className="min-w-0">
          <div className="flex items-center gap-3">
            <h1 className="text-xl sm:text-2xl md:text-3xl font-bold text-foreground leading-tight truncate">
              {title}
            </h1>
            {statusPill}
          </div>
          {description && (
            <p className="text-sm text-muted-foreground mt-0.5 leading-relaxed line-clamp-1">
              {description}
            </p>
          )}
        </div>
      </div>

      {/* Right: actions */}
      {actions && <div className="flex items-center gap-2 sm:gap-3 flex-shrink-0">{actions}</div>}
    </div>
  </div>
</div>;
```

**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 (
    <div>
      {/* Back link */}
      {backLink && ( ... )}

      {/* Header with muted background */}
      <div className="bg-muted/40 border-b border-border/40">
        <div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-space-md)] sm:px-[var(--cp-space-lg)] md:px-8 py-4">
          ...header content...
        </div>
      </div>

      {/* Content */}
      <div className="py-[var(--cp-space-lg)] sm:py-[var(--cp-space-xl)]">
        <div className="max-w-[var(--cp-page-max-width)] mx-auto px-[var(--cp-space-md)] sm:px-[var(--cp-space-lg)] md:px-8">
          <div className="space-y-6">
            {renderPageContent({ loading, error: error ?? undefined, children, onRetry, loadingFallback })}
          </div>
        </div>
      </div>
    </div>
  );
}
```

**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<SectionTone, string> = {
  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 (
    <div
      className={cn(
        "bg-card rounded-xl border border-border shadow-[var(--cp-shadow-1)] overflow-hidden",
        className
      )}
    >
      {/* Header */}
      <div className="bg-muted/40 px-6 py-4 border-b border-border/40">
        <div className="flex items-center justify-between gap-3">
          <div className="flex items-center gap-3 min-w-0">
            <div
              className={cn(
                "h-9 w-9 rounded-lg flex items-center justify-center flex-shrink-0",
                toneStyles[tone]
              )}
            >
              {icon}
            </div>
            <div className="min-w-0">
              <h3 className="text-sm font-semibold text-foreground">{title}</h3>
              {subtitle && <p className="text-xs text-muted-foreground mt-0.5">{subtitle}</p>}
            </div>
          </div>
          {actions && <div className="flex items-center gap-2 flex-shrink-0">{actions}</div>}
        </div>
      </div>

      {/* Content */}
      <div className="px-6 py-5">{children}</div>
    </div>
  );
}
```

**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
<PageLayout icon={<Server />} title="Subscriptions" description="Manage your active subscriptions">

// After
<PageLayout icon={<Server />} title="Subscriptions">
```

Do this for both the loading state and the main render.

**Step 2: Standardize content card borders**

```tsx
// Before
<div className="bg-card rounded-xl border border-border/60 shadow-[var(--cp-shadow-1)] overflow-hidden">

// After
<div className="bg-card rounded-xl border border-border shadow-[var(--cp-shadow-1)] overflow-hidden">
```

**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 ? (
    <Button variant="destructive" size="sm" as="a" href={`/account/subscriptions/${subscription.id}/cancel`}>
      Cancel Service
    </Button>
  ) : 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:
<SectionCard
  icon={<CreditCardIcon className="h-5 w-5" />}
  title="Billing Information"
  subtitle="Payment and invoices"
  tone="primary"
  actions={
    <Link
      href="/account/billing/invoices"
      className="text-sm font-medium text-primary hover:text-primary/80 transition-colors"
    >
      View Invoices
    </Link>
  }
>
  {/* billing content */}
</SectionCard>;
```

**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 `<StatusPill>` 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 `<PageLayout>`.

**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
```
