- Removed the PublicHelpPage component and streamlined navigation by adding a direct link to the Support page in the SiteFooter. - Updated the PublicShell component to redirect to the Support page instead of the Contact page. - Enhanced the CTABanner and HeroSection components with new text and improved call-to-action buttons. - Replaced the ServicesGrid component with ServicesCarousel for better service presentation. - Introduced new conversion service cards in the services data structure for improved service offerings. - Updated the PublicContactView and PublicSupportView components for better styling and accessibility.
29 KiB
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):
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):
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
"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.
"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.
"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 “Chat Button” 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:
{
/* 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>;
{
/* 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'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>;
{/* 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:
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:
// 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:
"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:
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:
{/* 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
ServicesGridexist - No imports reference
/helproute ContactSectionis only imported inPublicLandingView
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