Assist_Design/docs/plans/2026-03-05-internet-sim-page-redesign.md
barsa cde418b81e refactor: enhance infinite carousel functionality and styling
- Improved the useResponsiveCardWidth hook to prevent unnecessary state updates by tracking previous width.
- Introduced a new useCarouselInput hook to handle touch and keyboard navigation, streamlining event management.
- Updated the useInfiniteCarousel hook to utilize a ref for total items, enhancing performance and reliability.
- Enhanced styling for the SimPlansContent component, including improved layout and hover effects for better user experience.
- Added slide transition animations for tab changes in the PublicInternetPlans view, improving visual feedback during interactions.
- Updated internet tier descriptions and features for clarity and consistency across the application.
2026-03-05 11:18:34 +09:00

12 KiB

Internet & SIM Service Page Redesign - Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Redesign the public internet page to use a unified card with offering selector, and improve SIM page tab transitions + plan card design.

Architecture: Two independent UI refactors. Internet page merges ConsolidatedInternetCard + AvailablePlansSection into one component with a segmented control for offering type. SIM page adds direction-aware slide transitions and richer plan cards.

Tech Stack: React 19, Tailwind CSS, shadcn/ui, lucide-react icons


Task 1: Internet - Build Unified Internet Card with Offering Selector

Files:

  • Modify: apps/portal/src/features/services/views/PublicInternetPlans.tsx

Context: This file currently has two separate sections:

  1. ConsolidatedInternetCard (lines 59-209) - shows price range + 3 tier cards
  2. AvailablePlansSection (lines 653-731) - 3 expandable offering headers that open tier details below
  3. Helper components: PlanCardHeader (266-340), ExpandedTierDetails (345-465), MobilePlanCard (470-648)

The goal is to merge these into ONE card with a segmented offering type selector.

Step 1: Replace ConsolidatedInternetCard with UnifiedInternetCard

Delete the ConsolidatedInternetCard component (lines 59-209) and replace with a new UnifiedInternetCard component that:

  1. Accepts both consolidatedPlanData (for "All Plans" price ranges) AND plansByOffering (for per-offering exact prices)
  2. Has internal state: selectedOffering: "all" | "home10g" | "home1g" | "apartment"
  3. Header section:
    • Same Wifi icon + "NTT Fiber Internet" title
    • Price display that updates based on selectedOffering:
      • "all": shows full min~max range
      • specific offering: shows that offering's min~max (will often be exact price)
  4. Segmented control below header:
    • Pills: "All Plans" | "Home 10G" | "Home 1G" | "Apartment"
    • Active pill: bg-card text-foreground shadow-sm
    • Inactive: text-muted-foreground hover:text-foreground
    • Container: bg-muted/60 p-0.5 rounded-lg border border-border/60 (matches SIM tab style)
    • On mobile: horizontally scrollable with overflow-x-auto
    • Each pill shows the offering icon (Home/Building) and optional speed badge
  5. Tier cards grid (same 3 columns: Silver, Gold, Platinum):
    • When "All Plans": show price ranges per tier (current behavior from consolidated card)
    • When specific offering selected: show exact prices for that offering's tiers
    • Wrap the grid in a container with transition-opacity duration-200 for smooth price updates
  6. Footer: same setup fee + CTA button

Step 2: Remove obsolete components

Delete from PublicInternetPlans.tsx:

  • PlanCardHeader component (lines 266-340)
  • ExpandedTierDetails component (lines 345-465)
  • MobilePlanCard component (lines 470-648)
  • AvailablePlansSection component (lines 653-731)

Step 3: Update PublicInternetPlansContent render

In the PublicInternetPlansContent component's return JSX (starts line 961):

  • Replace the ConsolidatedInternetCard usage (lines 973-987) with UnifiedInternetCard
  • Pass it both consolidatedPlanData and plansByOffering
  • Remove the AvailablePlansSection usage (lines 989-995) entirely

Step 4: Verify and commit

cd /home/barsa/projects/customer_portal/customer-portal
pnpm type-check
pnpm lint

Expected: No type errors or lint issues.

git add apps/portal/src/features/services/views/PublicInternetPlans.tsx
git commit -m "feat: unified internet card with offering type selector"

Task 2: Internet - Clean Up Unused PublicOfferingCard

Files:

  • Check: apps/portal/src/features/services/components/internet/PublicOfferingCard.tsx

Context: After Task 1, the TierInfo type is still imported from PublicOfferingCard.tsx in PublicInternetPlans.tsx. The PublicOfferingCard component itself is no longer used.

Step 1: Move TierInfo type inline

In PublicInternetPlans.tsx, the import type { TierInfo } from "@/features/services/components/internet/PublicOfferingCard" needs to change. Define TierInfo directly in PublicInternetPlans.tsx:

interface TierInfo {
  tier: "Silver" | "Gold" | "Platinum";
  monthlyPrice: number;
  maxMonthlyPrice?: number;
  description: string;
  features: string[];
  pricingNote?: string;
}

Remove the import of TierInfo from PublicOfferingCard.

Step 2: Check if PublicOfferingCard is imported anywhere else

Search for all imports of PublicOfferingCard across the codebase. If no other files import it, it can be deleted. If other files import only TierInfo, move the type to a shared location or inline it.

Step 3: Verify and commit

pnpm type-check
pnpm lint
git add -A
git commit -m "refactor: inline TierInfo type, remove unused PublicOfferingCard"

Task 3: SIM - Direction-Aware Tab Transitions

Files:

  • Modify: apps/portal/src/features/services/components/sim/SimPlansContent.tsx

Context: The tab switcher is at lines 348-372. The plans grid is at lines 374-410. Currently uses animate-in fade-in duration-300 on the grid wrapper (line 377). The SIM_TABS array (lines 86-108) defines tab order: data-voice (0), data-only (1), voice-only (2).

Step 1: Add transition state tracking

In SimPlansContent component, add state to track slide direction:

import { useMemo, useRef } from "react";

// Inside SimPlansContent:
const prevTabRef = useRef(activeTab);
const slideDirection = useRef<"left" | "right">("left");

// Update direction when tab changes
if (prevTabRef.current !== activeTab) {
  const tabKeys = SIM_TABS.map(t => t.key);
  const prevIndex = tabKeys.indexOf(prevTabRef.current);
  const nextIndex = tabKeys.indexOf(activeTab);
  slideDirection.current = nextIndex > prevIndex ? "left" : "right";
  prevTabRef.current = activeTab;
}

Step 2: Replace the grid animation

Replace the current animate-in fade-in duration-300 on line 377 with a keyed wrapper that triggers CSS animation:

<div
  key={activeTab}
  className={cn(
    "space-y-8",
    slideDirection.current === "left"
      ? "animate-slide-fade-left"
      : "animate-slide-fade-right"
  )}
>

Step 3: Add the CSS animations

Check if tailwind.config.ts is in apps/portal/ and add custom keyframes:

// In tailwind.config.ts extend.keyframes:
"slide-fade-left": {
  "0%": { opacity: "0", transform: "translateX(24px)" },
  "100%": { opacity: "1", transform: "translateX(0)" },
},
"slide-fade-right": {
  "0%": { opacity: "0", transform: "translateX(-24px)" },
  "100%": { opacity: "1", transform: "translateX(0)" },
},

// In extend.animation:
"slide-fade-left": "slide-fade-left 300ms ease-out",
"slide-fade-right": "slide-fade-right 300ms ease-out",

Step 4: Add overflow-hidden to the container

On the plans grid container (<div id="plans" className="min-h-[280px]"> at line 375), add overflow-hidden to prevent horizontal scrollbar during slide:

<div id="plans" className="min-h-[280px] overflow-hidden">

Step 5: Verify and commit

pnpm type-check
pnpm lint
git add -A
git commit -m "feat: direction-aware slide transitions for SIM tab switching"

Task 4: SIM - Redesign Plan Cards

Files:

  • Modify: apps/portal/src/features/services/components/sim/SimPlansContent.tsx

Context: SimPlanCardCompact is defined at lines 152-229. Currently has:

  • Thin 0.5px top accent stripe (line 173-180)
  • Small signal icon (w-4 h-4) in a 9x9 box + data size in text-lg (line 193-203)
  • Small pricing via CardPricing component (line 207)
  • Plan name in text-xs (line 214)
  • Outline "Select Plan" button (line 217-225)

Step 1: Redesign the card

Replace the entire SimPlanCardCompact function (lines 152-229) with an enhanced version:

Key changes:

  1. Remove thin accent stripe - replace with subtle gradient overlay at top
  2. Data size hero: Bump from text-lg to text-2xl font-bold, make it the visual centerpiece
  3. Signal icon: Keep but make slightly larger (w-5 h-5) in a bigger container
  4. Price: Show directly as text-xl font-bold with yen symbol, more prominent than current CardPricing
  5. Plan name: Keep as subtitle in text-xs text-muted-foreground
  6. Hover effect: Add hover:-translate-y-0.5 hover:shadow-lg for lift effect
  7. Button: Change from variant="outline" to filled variant="default" for regular, variant="success" for family
  8. Background: Add subtle gradient - bg-gradient-to-br from-sky-50/50 to-transparent for regular (dark mode: dark:from-sky-950/20), emerald variant for family
  9. Border: Slightly thicker on hover with primary color
function SimPlanCardCompact({
  plan,
  isFamily,
  onSelect,
}: {
  plan: SimCatalogProduct;
  isFamily?: boolean;
  onSelect: (sku: string) => void;
}) {
  const displayPrice = plan.monthlyPrice ?? plan.unitPrice ?? plan.oneTimePrice ?? 0;

  return (
    <div
      className={cn(
        "group relative rounded-xl overflow-hidden transition-all duration-200",
        "hover:-translate-y-0.5 hover:shadow-lg",
        isFamily
          ? "border border-success/30 bg-gradient-to-br from-emerald-50/60 to-card dark:from-emerald-950/20 dark:to-card hover:border-success"
          : "border border-border bg-gradient-to-br from-sky-50/40 to-card dark:from-sky-950/15 dark:to-card hover:border-primary/50"
      )}
    >
      <div className="p-5">
        {isFamily && (
          <div className="flex items-center gap-1.5 mb-3">
            <Users className="w-3.5 h-3.5 text-success" />
            <span className="text-[10px] font-semibold text-success uppercase tracking-wider">
              Family Discount
            </span>
          </div>
        )}

        {/* Data size - hero element */}
        <div className="flex items-center gap-2.5 mb-4">
          <div
            className={cn(
              "w-10 h-10 rounded-xl flex items-center justify-center",
              isFamily ? "bg-success/10" : "bg-primary/8"
            )}
          >
            <Signal className={cn("w-5 h-5", isFamily ? "text-success" : "text-primary")} />
          </div>
          <span className="text-2xl font-bold text-foreground tracking-tight">
            {plan.simDataSize}
          </span>
        </div>

        {/* Price - prominent */}
        <div className="mb-1">
          <div className="flex items-baseline gap-0.5">
            <span className="text-xl font-bold text-foreground">
              ¥{displayPrice.toLocaleString()}
            </span>
            <span className="text-xs text-muted-foreground">/mo</span>
          </div>
          {isFamily && (
            <div className="text-[10px] text-success font-medium mt-0.5">Discounted price</div>
          )}
        </div>

        {/* Plan name */}
        <p className="text-xs text-muted-foreground mb-4 line-clamp-2 min-h-[2rem]">{plan.name}</p>

        {/* CTA - filled button */}
        <Button
          className="w-full"
          variant={isFamily ? "success" : "default"}
          size="sm"
          onClick={() => onSelect(plan.sku)}
          rightIcon={<ArrowRight className="w-3.5 h-3.5" />}
        >
          Select Plan
        </Button>
      </div>
    </div>
  );
}

Step 2: Verify the Button success variant exists

Check apps/portal/src/components/atoms/button.tsx for available variants. If success doesn't exist, use variant="default" with a custom className for family cards: className="w-full bg-success hover:bg-success/90 text-success-foreground".

Step 3: Verify and commit

pnpm type-check
pnpm lint
git add apps/portal/src/features/services/components/sim/SimPlansContent.tsx
git commit -m "feat: redesign SIM plan cards with improved visual hierarchy"

Task 5: Visual QA and Polish

Step 1: Run dev server and check both pages

pnpm --filter @customer-portal/portal dev

Check in browser:

  • /services/internet - Verify unified card, offering selector, price updates
  • /services/sim - Verify tab transitions, card design
  • Test mobile responsiveness (Chrome DevTools responsive mode)
  • Test dark mode if supported

Step 2: Fix any visual issues found during QA

Common things to check:

  • Segmented control alignment on mobile
  • Price animation smoothness
  • Slide transition not causing layout shift
  • Card hover effects working
  • Dark mode color contrast

Step 3: Final commit

git add -A
git commit -m "style: polish internet and SIM service page redesign"