Assist_Design/docs/plans/2026-03-04-landing-page-conversion-overhaul.md

931 lines
29 KiB
Markdown
Raw Normal View History

# Landing Page Conversion Overhaul — Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Restructure the public landing page into a conversion funnel that drives visitors to `/services`, with tabbed services carousel, support downloads, and full contact section restored.
**Architecture:** Replace static ServicesGrid with tabbed carousel (For You / For Business) using conversion-oriented cards with pricing. Restore full contact section (form + map + phone + address) at page bottom. Repurpose CTA Banner. Delete `/help` route. Update footer links.
**Tech Stack:** Next.js 15, React 19, Tailwind CSS, lucide-react icons, existing `ContactForm` component
**Design doc:** `docs/plans/2026-03-04-public-pages-restructuring-v2-design.md`
---
### Task 1: Extend Service Data with Conversion Card Fields
**Files:**
- Modify: `apps/portal/src/features/landing-page/data/services.tsx`
**Step 1: Add conversion card types and data**
Add a new `ConversionServiceCard` interface and update the `personalServices` and `businessServices` arrays with conversion fields. Keep existing exports intact for backward compatibility (other pages may use them).
Add after the existing `ServiceItem` interface (~line 24):
```tsx
export interface ConversionServiceCard {
title: string;
problemHook: string;
keyBenefit: string;
priceFrom?: string;
badge?: string;
icon: React.ReactNode;
href: string;
ctaLabel: string;
}
```
Add new conversion data arrays (after `businessServices`, before `services`):
```tsx
export const personalConversionCards: ConversionServiceCard[] = [
{
title: "Internet Plans",
problemHook: "Need reliable internet?",
keyBenefit: "NTT Fiber up to 10Gbps",
priceFrom: "¥3,200/mo",
icon: <Wifi className="h-7 w-7" />,
href: "/services/internet",
ctaLabel: "View Plans",
},
{
title: "Phone Plans",
problemHook: "Need a SIM card?",
keyBenefit: "Docomo network coverage",
priceFrom: "¥1,100/mo",
badge: "1st month free",
icon: <Smartphone className="h-7 w-7" />,
href: "/services/sim",
ctaLabel: "View Plans",
},
{
title: "VPN Service",
problemHook: "Missing shows from home?",
keyBenefit: "Stream US & UK content",
priceFrom: "¥2,500/mo",
icon: <Lock className="h-7 w-7" />,
href: "/services/vpn",
ctaLabel: "View Plans",
},
{
title: "Onsite Support",
problemHook: "Need hands-on help?",
keyBenefit: "English-speaking technicians",
icon: <Wrench className="h-7 w-7" />,
href: "/services/onsite",
ctaLabel: "Learn More",
},
];
export const businessConversionCards: ConversionServiceCard[] = [
{
title: "Office LAN Setup",
problemHook: "Setting up an office?",
keyBenefit: "Complete network infrastructure",
icon: <Server className="h-7 w-7" />,
href: "/services/business",
ctaLabel: "Get a Quote",
},
{
title: "Tech Support",
problemHook: "Need ongoing IT help?",
keyBenefit: "Onsite & remote support",
icon: <Wrench className="h-7 w-7" />,
href: "/services/onsite",
ctaLabel: "Get a Quote",
},
{
title: "Dedicated Internet",
problemHook: "Need guaranteed bandwidth?",
keyBenefit: "Enterprise-grade connectivity",
icon: <Building2 className="h-7 w-7" />,
href: "/services/business",
ctaLabel: "Get a Quote",
},
{
title: "Data Center",
problemHook: "Need hosting in Japan?",
keyBenefit: "Secure, reliable infrastructure",
icon: <Shield className="h-7 w-7" />,
href: "/services/business",
ctaLabel: "Get a Quote",
},
{
title: "Website Services",
problemHook: "Need a web presence?",
keyBenefit: "Construction & maintenance",
icon: <Code className="h-7 w-7" />,
href: "/services/business",
ctaLabel: "Get a Quote",
},
];
```
**Step 2: Verify no lint errors**
Run: `pnpm lint --filter @customer-portal/portal -- --no-warn`
**Step 3: Commit**
```
feat: add conversion card data for landing page services carousel
```
---
### Task 2: Create ServicesCarousel Component
**Files:**
- Create: `apps/portal/src/features/landing-page/components/ServicesCarousel.tsx`
**Step 1: Create the tabbed carousel component**
This component has:
- Tab switcher ("For You" / "For Business")
- Horizontal scrolling carousel with auto-scroll
- Conversion-oriented service cards with problem hook, benefit, price, badge, CTA
- Prev/next navigation buttons
```tsx
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import Link from "next/link";
import { ArrowRight, ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/shared/utils";
import { useInView } from "@/features/landing-page/hooks";
import {
personalConversionCards,
businessConversionCards,
type ConversionServiceCard,
} from "@/features/landing-page/data";
type Tab = "personal" | "business";
function ServiceConversionCard({ card }: { card: ConversionServiceCard }) {
return (
<Link href={card.href} className="group flex-shrink-0 w-[260px] sm:w-[280px] snap-start">
<article
data-service-card
className="h-full rounded-2xl bg-card border border-border/60 px-6 py-7 shadow-sm hover:shadow-md hover:border-primary/30 transition-all duration-300 group-hover:-translate-y-1 flex flex-col"
>
{card.badge && (
<span className="inline-flex self-start items-center rounded-full bg-success/10 text-success px-2.5 py-0.5 text-xs font-semibold mb-3">
{card.badge}
</span>
)}
<div className="mb-4 text-primary">{card.icon}</div>
<p className="text-sm text-muted-foreground mb-1">{card.problemHook}</p>
<h3 className="text-lg font-bold text-foreground mb-1">{card.title}</h3>
<p className="text-sm text-muted-foreground mb-3 flex-grow">{card.keyBenefit}</p>
{card.priceFrom && (
<p className="text-lg font-bold text-primary mb-4">from {card.priceFrom}</p>
)}
<span className="inline-flex items-center gap-1.5 text-sm font-semibold text-primary group-hover:gap-2.5 transition-all mt-auto">
{card.ctaLabel}
<ArrowRight className="h-4 w-4" />
</span>
</article>
</Link>
);
}
export function ServicesCarousel() {
const [activeTab, setActiveTab] = useState<Tab>("personal");
const carouselRef = useRef<HTMLDivElement>(null);
const itemWidthRef = useRef(0);
const isScrollingRef = useRef(false);
const autoScrollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
const [sectionRef, isInView] = useInView();
const cards = activeTab === "personal" ? personalConversionCards : businessConversionCards;
const computeItemWidth = useCallback(() => {
const container = carouselRef.current;
if (!container) return;
const card = container.querySelector<HTMLElement>("[data-service-card]");
if (!card) return;
const style = getComputedStyle(container);
const gap = parseFloat(style.columnGap || style.gap || "0") || 24;
itemWidthRef.current = card.getBoundingClientRect().width + gap;
}, []);
const scrollByOne = useCallback((direction: 1 | -1) => {
const container = carouselRef.current;
if (!container || !itemWidthRef.current || isScrollingRef.current) return;
isScrollingRef.current = true;
container.scrollBy({ left: direction * itemWidthRef.current, behavior: "smooth" });
setTimeout(() => {
isScrollingRef.current = false;
}, 500);
}, []);
const startAutoScroll = useCallback(() => {
if (autoScrollTimerRef.current) clearInterval(autoScrollTimerRef.current);
autoScrollTimerRef.current = setInterval(() => scrollByOne(1), 5000);
}, [scrollByOne]);
const stopAutoScroll = useCallback(() => {
if (autoScrollTimerRef.current) {
clearInterval(autoScrollTimerRef.current);
autoScrollTimerRef.current = null;
}
}, []);
useEffect(() => {
computeItemWidth();
window.addEventListener("resize", computeItemWidth);
startAutoScroll();
return () => {
window.removeEventListener("resize", computeItemWidth);
stopAutoScroll();
};
}, [computeItemWidth, startAutoScroll, stopAutoScroll]);
// Reset scroll position when tab changes
useEffect(() => {
if (carouselRef.current) {
carouselRef.current.scrollTo({ left: 0, behavior: "smooth" });
}
computeItemWidth();
}, [activeTab, computeItemWidth]);
const handlePrev = useCallback(() => {
scrollByOne(-1);
startAutoScroll();
}, [scrollByOne, startAutoScroll]);
const handleNext = useCallback(() => {
scrollByOne(1);
startAutoScroll();
}, [scrollByOne, startAutoScroll]);
return (
<section
ref={sectionRef as React.RefObject<HTMLElement>}
className={cn(
"relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-surface-sunken/30 py-14 sm:py-16 transition-all duration-700",
isInView ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
)}
>
<div className="mx-auto max-w-6xl px-6 sm:px-10 lg:px-14">
{/* Header + Tabs */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
<div>
<h2 className="text-2xl sm:text-3xl font-extrabold text-foreground">Our Services</h2>
<p className="mt-1 text-base text-muted-foreground">
Everything you need to stay connected in Japan
</p>
</div>
<div className="flex bg-muted rounded-full p-1 self-start">
<button
type="button"
onClick={() => setActiveTab("personal")}
className={cn(
"px-4 py-2 text-sm font-semibold rounded-full transition-all",
activeTab === "personal"
? "bg-foreground text-background shadow-sm"
: "text-muted-foreground hover:text-foreground"
)}
>
For You
</button>
<button
type="button"
onClick={() => setActiveTab("business")}
className={cn(
"px-4 py-2 text-sm font-semibold rounded-full transition-all",
activeTab === "business"
? "bg-foreground text-background shadow-sm"
: "text-muted-foreground hover:text-foreground"
)}
>
For Business
</button>
</div>
</div>
{/* Carousel */}
<div className="relative" onMouseEnter={stopAutoScroll} onMouseLeave={startAutoScroll}>
<div
ref={carouselRef}
className="flex gap-5 overflow-x-auto scroll-smooth pb-4 snap-x snap-mandatory"
style={{ scrollbarWidth: "none", WebkitOverflowScrolling: "touch" }}
>
{cards.map(card => (
<ServiceConversionCard key={card.title} card={card} />
))}
</div>
{/* Navigation buttons */}
<div className="flex justify-end gap-2 mt-4">
<button
type="button"
aria-label="Scroll left"
onClick={handlePrev}
className="h-9 w-9 rounded-full border border-border bg-card text-foreground shadow-sm hover:bg-muted transition-colors flex items-center justify-center"
>
<ChevronLeft className="h-4 w-4" />
</button>
<button
type="button"
aria-label="Scroll right"
onClick={handleNext}
className="h-9 w-9 rounded-full border border-border bg-card text-foreground shadow-sm hover:bg-muted transition-colors flex items-center justify-center"
>
<ChevronRight className="h-4 w-4" />
</button>
</div>
</div>
</div>
</section>
);
}
```
**Step 2: Verify no lint errors**
Run: `pnpm lint --filter @customer-portal/portal -- --no-warn`
**Step 3: Commit**
```
feat: create ServicesCarousel with tabbed conversion cards
```
---
### Task 3: Create SupportDownloadsSection Component
**Files:**
- Create: `apps/portal/src/features/landing-page/components/SupportDownloadsSection.tsx`
**Step 1: Create the component**
Extracted from old landing page and from the support page pattern. Uses existing `supportDownloads` data.
```tsx
"use client";
import Image from "next/image";
import { Download } from "lucide-react";
import { cn } from "@/shared/utils";
import { useInView } from "@/features/landing-page/hooks";
import { supportDownloads } from "@/features/landing-page/data";
export function SupportDownloadsSection() {
const [ref, isInView] = useInView();
return (
<section
ref={ref as React.RefObject<HTMLElement>}
className={cn(
"py-14 sm:py-16 transition-all duration-700",
isInView ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
)}
>
<div className="mx-auto max-w-5xl px-6 sm:px-10 lg:px-14">
<h2 className="text-center text-2xl sm:text-3xl font-extrabold text-foreground tracking-tight mb-2">
Remote Support
</h2>
<p className="text-center text-muted-foreground mb-8">
Download one of these tools so our technicians can assist you remotely.
</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
{supportDownloads.map(tool => (
<a
key={tool.title}
href={tool.href}
target="_blank"
rel="noopener noreferrer"
className="group bg-card rounded-2xl border border-border/60 p-6 hover:border-primary/40 hover:shadow-md transition-all duration-200"
>
<div className="flex items-start gap-5">
<div className="w-16 h-16 rounded-xl bg-muted/30 flex items-center justify-center shrink-0 overflow-hidden">
<Image
src={tool.image}
alt={tool.title}
width={48}
height={48}
className="object-contain"
/>
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<h3 className="font-bold text-foreground group-hover:text-primary transition-colors">
{tool.title}
</h3>
<Download className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" />
</div>
<p className="text-sm text-muted-foreground leading-relaxed mb-2">
{tool.description}
</p>
<p className="text-xs font-medium text-primary">{tool.useCase}</p>
</div>
</div>
</a>
))}
</div>
</div>
</section>
);
}
```
**Step 2: Verify no lint errors**
Run: `pnpm lint --filter @customer-portal/portal -- --no-warn`
**Step 3: Commit**
```
feat: create SupportDownloadsSection component for landing page
```
---
### Task 4: Create ContactSection Component
**Files:**
- Create: `apps/portal/src/features/landing-page/components/ContactSection.tsx`
**Step 1: Create the full contact section**
Restored from the old landing page layout — form on the left with chat/phone info, map + address on the right. Uses the existing `ContactForm` component.
```tsx
"use client";
import { Mail, MapPin, MessageSquare, PhoneCall, Train } from "lucide-react";
import { cn } from "@/shared/utils";
import { useInView } from "@/features/landing-page/hooks";
import { ContactForm } from "@/features/support/components";
export function ContactSection() {
const [ref, isInView] = useInView();
return (
<section
id="contact"
ref={ref as React.RefObject<HTMLElement>}
className={cn(
"relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-surface-sunken/30 py-14 sm:py-16 transition-all duration-700",
isInView ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
)}
>
<div className="max-w-6xl mx-auto px-6 sm:px-10 lg:px-14 space-y-6">
<h2 className="text-2xl sm:text-3xl font-extrabold text-foreground">
Tell Us What You Need
</h2>
<div className="rounded-2xl bg-card border border-border/60 shadow-sm p-6 sm:p-8">
<div className="grid grid-cols-1 lg:grid-cols-[1.1fr_0.9fr] gap-10 lg:gap-12">
{/* Left: Form + Contact Methods */}
<div className="space-y-6">
<div className="flex items-center gap-2 text-primary font-bold text-lg">
<Mail className="h-5 w-5" />
<span>By Online Form (Anytime)</span>
</div>
<ContactForm className="border-0 p-0 rounded-none bg-transparent" />
<div className="flex flex-col gap-3 pt-2">
<div className="inline-flex items-center gap-2 text-primary font-semibold">
<MessageSquare className="h-5 w-5" />
<span>By Chat (Anytime)</span>
</div>
<p className="text-sm text-muted-foreground">
Click the bottom right &ldquo;Chat Button&rdquo; to reach our team anytime.
</p>
<div className="inline-flex items-center gap-2 text-primary font-semibold">
<PhoneCall className="h-5 w-5" />
<span>By Phone (9:30-18:00 JST)</span>
</div>
<div className="text-sm text-muted-foreground">
<p className="font-semibold text-foreground">Toll Free within Japan</p>
<p className="text-lg font-bold text-primary">0120-660-470</p>
<p className="font-semibold text-foreground mt-1">From Overseas</p>
<p className="text-lg font-bold text-primary">+81-3-3560-1006</p>
</div>
</div>
</div>
{/* Right: Map + Address */}
<div className="space-y-6">
<div className="w-full rounded-2xl overflow-hidden shadow-md border border-border/60 bg-card aspect-[4/3]">
<iframe
title="Assist Solutions Corp Map"
src="https://www.google.com/maps?q=Assist+Solutions+Corp,+3-8-2+Higashi+Azabu,+Minato-ku,+Tokyo&output=embed"
className="w-full h-full"
loading="lazy"
allowFullScreen
referrerPolicy="no-referrer-when-downgrade"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="rounded-2xl bg-card shadow-sm border border-border/60 p-5 space-y-2">
<div className="inline-flex items-center gap-2 text-primary font-semibold">
<Train className="h-5 w-5" />
<span>Access</span>
</div>
<p className="text-sm text-muted-foreground">
Subway Oedo Line / Nanboku Line
<br />
Short walk from Exit 6, Azabu-Juban Station
</p>
</div>
<div className="rounded-2xl bg-card shadow-sm border border-border/60 p-5 space-y-2">
<div className="inline-flex items-center gap-2 text-primary font-semibold">
<MapPin className="h-5 w-5" />
<span>Address</span>
</div>
<p className="text-sm text-muted-foreground">
3F Azabu Maruka Bldg.,
<br />
3-8-2 Higashi Azabu, Minato-ku,
<br />
Tokyo 106-0044
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}
```
**Important note:** The `ContactForm` component renders its own card wrapper (`bg-card rounded-2xl border ...`). We pass `className="border-0 p-0 rounded-none bg-transparent"` to strip it since the outer card provides the wrapper here. Verify this works — if `ContactForm` doesn't pass `className` through to the outer wrapper correctly, you may need to adjust. Check `ContactForm.tsx:74` where `cn("bg-card rounded-2xl border border-border/60 p-6", className)` confirms it does merge classNames correctly.
**Step 2: Verify no lint errors**
Run: `pnpm lint --filter @customer-portal/portal -- --no-warn`
**Step 3: Commit**
```
feat: create ContactSection component with form, map, and contact info
```
---
### Task 5: Update HeroSection — Problem-First Copy + #contact CTA
**Files:**
- Modify: `apps/portal/src/features/landing-page/components/HeroSection.tsx`
**Step 1: Update hero heading, subtitle, and CTAs**
Change lines 46-70:
```tsx
{
/* OLD heading (lines 46-49): */
}
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-extrabold leading-tight text-foreground">
<span className="block">A One Stop Solution</span>
<span className="block text-primary mt-2">for Your IT Needs</span>
</h1>;
{
/* NEW heading: */
}
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-extrabold leading-tight text-foreground">
<span className="block">Just Moved to Japan?</span>
<span className="block text-primary mt-2">Get Connected in English</span>
</h1>;
```
```tsx
{
/* OLD subtitle (lines 50-53): */
}
<p className="mt-6 text-base sm:text-lg text-muted-foreground leading-relaxed font-semibold max-w-2xl mx-auto">
From internet and mobile to VPN and on-site tech support — we handle it all in English so you
don&apos;t have to.
</p>;
{
/* NEW subtitle: */
}
<p className="mt-6 text-base sm:text-lg text-muted-foreground leading-relaxed font-semibold max-w-2xl mx-auto">
Internet, phone, VPN and IT support — set up in days, not weeks. No Japanese needed.
</p>;
```
```tsx
{/* OLD CTAs (lines 58-69): */}
<Button as="a" href="/services" variant="pill" size="lg" rightIcon={<ArrowRight className="h-5 w-5" />}>
Browse Services
</Button>
<Button as="a" href="/contact" variant="pillOutline" size="lg">
Need Assistance?
</Button>
{/* NEW CTAs: */}
<Button as="a" href="/services" variant="pill" size="lg" rightIcon={<ArrowRight className="h-5 w-5" />}>
Find Your Plan
</Button>
<Button as="a" href="#contact" variant="pillOutline" size="lg">
Talk to Us
</Button>
```
**Step 2: Verify no lint errors**
Run: `pnpm lint --filter @customer-portal/portal -- --no-warn`
**Step 3: Commit**
```
feat: update hero to problem-first copy with #contact CTA
```
---
### Task 6: Update CTABanner — Remove Phone, Update CTAs
**Files:**
- Modify: `apps/portal/src/features/landing-page/components/CTABanner.tsx`
**Step 1: Rewrite CTABanner**
Replace the entire file content:
```tsx
import { ArrowRight } from "lucide-react";
import { Button } from "@/components/atoms/button";
export function CTABanner() {
return (
<section
aria-label="Call to action"
className="relative left-1/2 right-1/2 w-screen -translate-x-1/2 bg-primary-soft"
>
<div className="mx-auto max-w-3xl px-6 sm:px-10 lg:px-14 py-14 sm:py-16 text-center">
<h2 className="text-2xl sm:text-3xl font-extrabold text-foreground">
Ready to Get Set Up?
</h2>
<p className="mt-2 text-base text-muted-foreground">
No Japanese required. Our English-speaking team is here to help.
</p>
<div className="mt-6 flex flex-col sm:flex-row items-center justify-center gap-3">
<Button
as="a"
href="/services"
variant="pill"
size="lg"
rightIcon={<ArrowRight className="h-5 w-5" />}
>
Find Your Plan
</Button>
<Button as="a" href="#contact" variant="pillOutline" size="lg">
Talk to Us
</Button>
</div>
</div>
</section>
);
}
```
**Step 2: Verify no lint errors**
Run: `pnpm lint --filter @customer-portal/portal -- --no-warn`
**Step 3: Commit**
```
feat: repurpose CTABanner for conversion — remove phone, update CTAs
```
---
### Task 7: Update Barrel Exports + Compose New Landing Page
**Files:**
- Modify: `apps/portal/src/features/landing-page/components/index.ts`
- Modify: `apps/portal/src/features/landing-page/views/PublicLandingView.tsx`
**Step 1: Update barrel exports**
Replace `apps/portal/src/features/landing-page/components/index.ts`:
```ts
// Landing page sections
export { HeroSection } from "./HeroSection";
export { TrustStrip } from "./TrustStrip";
export { ServicesCarousel } from "./ServicesCarousel";
export { WhyUsSection } from "./WhyUsSection";
export { CTABanner } from "./CTABanner";
export { SupportDownloadsSection } from "./SupportDownloadsSection";
export { ContactSection } from "./ContactSection";
```
Note: `ServicesGrid` export removed — replaced by `ServicesCarousel`.
**Step 2: Rewrite PublicLandingView with 7-section composition**
Replace the entire content of `apps/portal/src/features/landing-page/views/PublicLandingView.tsx`:
```tsx
"use client";
import { ArrowRight } from "lucide-react";
import { Button } from "@/components/atoms/button";
import { useStickyCta } from "@/features/landing-page/hooks";
import {
HeroSection,
TrustStrip,
ServicesCarousel,
WhyUsSection,
CTABanner,
SupportDownloadsSection,
ContactSection,
} from "@/features/landing-page/components";
export function PublicLandingView() {
const { heroCTARef, showStickyCTA } = useStickyCta();
return (
<div className="space-y-0 pb-8 pt-0">
<HeroSection heroCTARef={heroCTARef} />
<TrustStrip />
<ServicesCarousel />
<WhyUsSection />
<CTABanner />
<SupportDownloadsSection />
<ContactSection />
{/* Sticky Mobile CTA */}
{showStickyCTA && (
<div className="fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur-sm border-t border-border p-4 z-50 md:hidden animate-in slide-in-from-bottom-4 duration-300">
<Button
as="a"
href="/services"
variant="pill"
size="lg"
rightIcon={<ArrowRight className="h-5 w-5" />}
className="w-full shadow-lg"
>
Find Your Plan
</Button>
</div>
)}
</div>
);
}
```
**Step 3: Verify no lint errors**
Run: `pnpm lint --filter @customer-portal/portal -- --no-warn`
**Step 4: Commit**
```
feat: compose landing page with 7-section conversion funnel
```
---
### Task 8: Delete ServicesGrid + /help Route
**Files:**
- Delete: `apps/portal/src/features/landing-page/components/ServicesGrid.tsx`
- Delete: `apps/portal/src/app/(public)/(site)/help/page.tsx`
**Step 1: Delete ServicesGrid.tsx**
This file is no longer imported anywhere (barrel export was updated in Task 7).
Run: `rm apps/portal/src/features/landing-page/components/ServicesGrid.tsx`
**Step 2: Delete /help route**
Run: `rm apps/portal/src/app/\(public\)/\(site\)/help/page.tsx`
Then check if the help directory is empty and remove it:
Run: `rmdir apps/portal/src/app/\(public\)/\(site\)/help/ 2>/dev/null || true`
**Step 3: Verify no lingering imports of ServicesGrid**
Run grep to confirm no file imports `ServicesGrid`:
```bash
pnpm exec grep -r "ServicesGrid" apps/portal/src/ --include="*.ts" --include="*.tsx"
```
Expected: no results.
**Step 4: Verify no lint errors**
Run: `pnpm lint --filter @customer-portal/portal -- --no-warn`
**Step 5: Commit**
```
refactor: remove ServicesGrid and /help route
```
---
### Task 9: Update SiteFooter Links
**Files:**
- Modify: `apps/portal/src/components/organisms/SiteFooter/SiteFooter.tsx`
**Step 1: Split "Support & Contact" into separate links**
In the Company links section (lines 85-110), change the "Support & Contact" link:
```tsx
{/* OLD (lines 94-101): */}
<li>
<Link
href="/contact"
className="text-muted-foreground hover:text-foreground transition-colors"
>
Support & Contact
</Link>
</li>
{/* NEW — replace with two separate links: */}
<li>
<Link
href="/support"
className="text-muted-foreground hover:text-foreground transition-colors"
>
Support
</Link>
</li>
<li>
<Link
href="/contact"
className="text-muted-foreground hover:text-foreground transition-colors"
>
Contact
</Link>
</li>
```
**Step 2: Verify no lint errors**
Run: `pnpm lint --filter @customer-portal/portal -- --no-warn`
**Step 3: Commit**
```
refactor: split footer "Support & Contact" into separate links
```
---
### Task 10: Final Verification
**Step 1: Run full type check**
Run: `pnpm type-check`
Expected: no errors related to landing-page, support, or footer components.
**Step 2: Run full lint check**
Run: `pnpm lint`
Expected: no new errors. Warnings are acceptable.
**Step 3: Verify no dangling imports**
Run grep to confirm:
- No imports of `ServicesGrid` exist
- No imports reference `/help` route
- `ContactSection` is only imported in `PublicLandingView`
```bash
pnpm exec grep -rn "ServicesGrid\|from.*help/page\|/help" apps/portal/src/ --include="*.ts" --include="*.tsx" | grep -v node_modules | grep -v ".next"
```
**Step 4: Commit any fixes**
If there are type or lint errors, fix them and commit:
```
fix: resolve lint/type errors from landing page restructuring
```