Assist_Design/docs/plans/2026-03-05-internet-sim-page-redesign.md

364 lines
12 KiB
Markdown
Raw Normal View History

# 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**
```bash
cd /home/barsa/projects/customer_portal/customer-portal
pnpm type-check
pnpm lint
```
Expected: No type errors or lint issues.
```bash
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`:
```typescript
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**
```bash
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:
```typescript
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:
```tsx
<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:
```javascript
// 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:
```tsx
<div id="plans" className="min-h-[280px] overflow-hidden">
```
**Step 5: Verify and commit**
```bash
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
```tsx
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**
```bash
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**
```bash
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**
```bash
git add -A
git commit -m "style: polish internet and SIM service page redesign"
```