tema ff870c9f4f Update Service Pages Metadata and Content for Enhanced User Engagement
- Revised metadata titles and descriptions across various service pages to better reflect offerings and improve SEO.
- Updated content in the About Us, Contact, and Support pages to emphasize English support and services for expats in Japan.
- Removed TV Services references from the portal, streamlining service offerings and focusing on core services.
- Enhanced service descriptions to clarify benefits and features, ensuring users understand the value of each service.
2026-01-20 17:19:00 +09:00

266 lines
8.8 KiB
TypeScript

"use client";
import {
ShieldCheck,
Router,
Globe,
Tv,
Wifi,
Package,
Headphones,
CreditCard,
Play,
} from "lucide-react";
import { usePublicVpnCatalog } from "@/features/services/hooks";
import { LoadingCard } from "@/components/atoms";
import { AsyncBlock } from "@/components/molecules/AsyncBlock/AsyncBlock";
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
import { VpnPlanCard } from "@/features/services/components/vpn/VpnPlanCard";
import { ServicesBackLink } from "@/features/services/components/base/ServicesBackLink";
import { ServicesHero } from "@/features/services/components/base/ServicesHero";
import { useServicesBasePath } from "@/features/services/hooks/useServicesBasePath";
import {
ServiceHighlights,
type HighlightFeature,
} from "@/features/services/components/base/ServiceHighlights";
/**
* Public VPN Plans View
*
* Displays VPN plans for unauthenticated users.
*/
export function PublicVpnPlansView() {
const servicesBasePath = useServicesBasePath();
const { data, error } = usePublicVpnCatalog();
const vpnPlans = data?.plans || [];
const activationFees = data?.activationFees || [];
// Simple loading check: show skeleton until we have data or an error
const isLoading = !data && !error;
if (isLoading || error) {
return (
<div className="max-w-6xl mx-auto px-4">
<ServicesBackLink href={servicesBasePath} label="Back to Services" />
<AsyncBlock
isLoading={isLoading}
error={error}
loadingText="Loading VPN plans..."
variant="page"
>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto">
{Array.from({ length: 4 }).map((_, index) => (
<LoadingCard key={index} className="h-64" />
))}
</div>
</AsyncBlock>
</div>
);
}
const vpnFeatures: HighlightFeature[] = [
{
icon: <Router className="h-6 w-6" />,
title: "Zero Setup Required",
description: "Router arrives pre-configured. Just plug in and you're connected",
highlight: "Plug & play",
},
{
icon: <Tv className="h-6 w-6" />,
title: "Stream from Home",
description: "Watch Netflix, Hulu, BBC iPlayer and more from the US or UK",
highlight: "Your content",
},
{
icon: <Globe className="h-6 w-6" />,
title: "US & UK Servers",
description: "Choose San Francisco for US content or London for UK content",
highlight: "2 regions",
},
{
icon: <Wifi className="h-6 w-6" />,
title: "Dedicated VPN WiFi",
description: "Separate network for VPN. Your regular internet stays fast",
highlight: "No slowdown",
},
{
icon: <Package className="h-6 w-6" />,
title: "All-Inclusive Rental",
description: "Router rental included in your monthly fee. Nothing extra to buy",
highlight: "Simple pricing",
},
{
icon: <Headphones className="h-6 w-6" />,
title: "English Support",
description: "Questions? Our English-speaking team is here to help",
highlight: "We speak your language",
},
];
return (
<div className="max-w-6xl mx-auto px-4 pb-16">
<ServicesBackLink href={servicesBasePath} label="Back to Services" />
<ServicesHero
title="Stream Your Favorites from Home"
description="Missing shows from back home? Our VPN lets you watch US and UK content in Japan. Pre-configured router, just plug in and stream."
/>
{/* Service Highlights */}
<ServiceHighlights features={vpnFeatures} className="mb-12" />
{vpnPlans.length > 0 ? (
<div className="mb-8">
<div className="text-center mb-8">
<span className="text-sm font-semibold text-primary uppercase tracking-wider">
Choose Your Region
</span>
<h2 className="text-2xl font-bold text-foreground mt-1">Available Plans</h2>
<p className="text-sm text-muted-foreground mt-2">
Select one region per router rental
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto">
{vpnPlans.map(plan => (
<VpnPlanCard key={plan.id} plan={plan} />
))}
</div>
{activationFees.length > 0 && (
<AlertBanner variant="info" className="mt-6 max-w-4xl mx-auto" title="Activation Fee">
A one-time activation fee of ¥3,000 applies per router rental. Tax (10%) not included.
</AlertBanner>
)}
</div>
) : (
<div className="text-center py-12">
<ShieldCheck className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<h3 className="text-lg font-medium text-foreground mb-2">No VPN Plans Available</h3>
<p className="text-muted-foreground mb-6">
We couldn&apos;t find any VPN plans available at this time.
</p>
<ServicesBackLink
href={servicesBasePath}
label="Back to Services"
align="center"
className="mt-4 mb-0"
/>
</div>
)}
{/* How It Works Section */}
<VpnHowItWorksSection />
<AlertBanner variant="warning" title="Important Disclaimer" className="mb-8">
<p className="text-sm">
Content subscriptions are NOT included in the VPN package. Our VPN service establishes a
network connection that virtually locates you in the designated server location. Not all
services can be unblocked. We do not guarantee access to any specific website or streaming
service quality.
</p>
</AlertBanner>
</div>
);
}
interface HowItWorksStepProps {
number: number;
icon: React.ReactNode;
title: string;
description: string;
}
function HowItWorksStep({ number, icon, title, description }: HowItWorksStepProps) {
return (
<div className="flex flex-col items-center text-center flex-1 min-w-0">
{/* Icon with number badge */}
<div className="relative mb-4">
<div className="flex h-16 w-16 items-center justify-center rounded-xl bg-gray-50 border border-gray-200 text-primary shadow-sm">
{icon}
</div>
{/* Number badge */}
<div className="absolute -top-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full bg-primary text-white text-xs font-bold shadow-sm">
{number}
</div>
</div>
{/* Content */}
<h4 className="font-semibold text-foreground mb-2">{title}</h4>
<p className="text-sm text-muted-foreground leading-relaxed max-w-[180px]">{description}</p>
</div>
);
}
function VpnHowItWorksSection() {
const steps = [
{
icon: <CreditCard className="h-6 w-6" />,
title: "Sign Up",
description: "Create your account to get started",
},
{
icon: <Globe className="h-6 w-6" />,
title: "Choose Region",
description: "Select US (San Francisco) or UK (London)",
},
{
icon: <Package className="h-6 w-6" />,
title: "Place Order",
description: "Complete checkout and receive router",
},
{
icon: <Play className="h-6 w-6" />,
title: "Connect & Stream",
description: "Plug in, connect devices, enjoy",
},
];
return (
<section className="bg-card rounded-xl border border-border shadow-[var(--cp-shadow-1)] p-8 mb-8">
{/* Header */}
<div className="text-center mb-8">
<span className="text-sm font-semibold text-primary uppercase tracking-wider">
Simple Setup
</span>
<h3 className="text-2xl font-bold text-foreground mt-1">How It Works</h3>
</div>
{/* Steps with connecting line */}
<div className="relative">
{/* Connecting line - hidden on mobile */}
<div className="hidden md:block absolute top-8 left-[12%] right-[12%] h-0.5 bg-gray-200" />
{/* Curved path SVG for visual connection - hidden on mobile */}
<svg
className="hidden md:block absolute top-[30px] left-0 right-0 w-full h-4 pointer-events-none"
preserveAspectRatio="none"
>
<path
d="M 12% 8 Q 30% 8, 37.5% 8 Q 45% 8, 50% 8 Q 55% 8, 62.5% 8 Q 70% 8, 88% 8"
fill="none"
stroke="#e5e7eb"
strokeWidth="2"
strokeDasharray="6 4"
/>
</svg>
{/* Steps grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-8 relative z-10">
{steps.map((step, index) => (
<HowItWorksStep
key={index}
number={index + 1}
icon={step.icon}
title={step.title}
description={step.description}
/>
))}
</div>
</div>
</section>
);
}
export default PublicVpnPlansView;