- Updated the AppShell and Sidebar components for improved layout and spacing. - Replaced font colors in the Logo component for better visibility. - Adjusted the PageLayout component to utilize backLink props instead of breadcrumbs for navigation consistency. - Removed unnecessary description props from multiple PageLayout instances across various views to simplify the codebase. - Introduced SectionCard component in OrderDetail for better organization of billing information. - Enhanced utility styles in CSS for improved typography and layout consistency.
22 KiB
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:
/* 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:
/* 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:
// 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:
// 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:
// 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:
// 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
// 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:
{
/* 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:
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
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
// 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
// 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:
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
// 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
// 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:
// 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
// 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:
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
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
// 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
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
backLink={{ label: "Back to Cases", href: "/account/support" }}
Remove description. Use SectionCard for conversation and meta sections.
Step 3: NewSupportCaseView — replace breadcrumbs with backLink
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