Refactor internet and SIM plan components for improved UI and functionality
- Removed unused imports and components for cleaner code. - Updated styling and layout in InternetPlanCard for better visual consistency. - Introduced CatalogBackLink for navigation in InternetConfigureContainer and SimConfigureView. - Enhanced loading states and error handling in InternetPlans and SimPlans views. - Improved button interactions and accessibility in ActivationForm and SimConfigureView. - Streamlined plan display logic and added dynamic features in various components.
This commit is contained in:
parent
3d17f36c2f
commit
da91a51323
@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/atoms/button";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type Alignment = "left" | "center" | "right";
|
||||
|
||||
interface CatalogBackLinkProps {
|
||||
href: string;
|
||||
label?: string;
|
||||
align?: Alignment;
|
||||
className?: string;
|
||||
buttonClassName?: string;
|
||||
icon?: ReactNode;
|
||||
}
|
||||
|
||||
const alignmentMap: Record<Alignment, string> = {
|
||||
left: "justify-start",
|
||||
center: "justify-center",
|
||||
right: "justify-end",
|
||||
};
|
||||
|
||||
export function CatalogBackLink({
|
||||
href,
|
||||
label = "Back",
|
||||
align = "left",
|
||||
className,
|
||||
buttonClassName,
|
||||
icon = <ArrowLeftIcon className="w-4 h-4" />,
|
||||
}: CatalogBackLinkProps) {
|
||||
return (
|
||||
<div className={cn("mb-6 flex", alignmentMap[align], className)}>
|
||||
<Button
|
||||
as="a"
|
||||
href={href}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
leftIcon={icon}
|
||||
className={cn("text-gray-600 hover:text-gray-900", buttonClassName)}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export type { CatalogBackLinkProps };
|
||||
@ -0,0 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
type Alignment = "left" | "center";
|
||||
|
||||
interface CatalogHeroProps {
|
||||
title: string;
|
||||
description: string;
|
||||
align?: Alignment;
|
||||
eyebrow?: ReactNode;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const alignmentMap: Record<Alignment, string> = {
|
||||
left: "text-left items-start",
|
||||
center: "text-center items-center",
|
||||
};
|
||||
|
||||
export function CatalogHero({
|
||||
title,
|
||||
description,
|
||||
align = "center",
|
||||
eyebrow,
|
||||
children,
|
||||
className,
|
||||
}: CatalogHeroProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-4 mb-12",
|
||||
alignmentMap[align],
|
||||
className,
|
||||
align === "center" ? "mx-auto max-w-3xl" : ""
|
||||
)}
|
||||
>
|
||||
{eyebrow ? <div className="text-sm font-medium text-blue-700">{eyebrow}</div> : null}
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 leading-tight">{title}</h1>
|
||||
<p className="text-lg text-gray-600 leading-relaxed">{description}</p>
|
||||
{children ? <div className="mt-2 w-full">{children}</div> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export type { CatalogHeroProps };
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { AnimatedCard } from "@/components/molecules";
|
||||
import { Button } from "@/components/atoms/button";
|
||||
import { CurrencyYenIcon, ArrowRightIcon } from "@heroicons/react/24/outline";
|
||||
import { ArrowRightIcon } from "@heroicons/react/24/outline";
|
||||
import type {
|
||||
InternetPlanCatalogItem,
|
||||
InternetInstallationCatalogItem,
|
||||
@ -27,6 +27,8 @@ export function InternetPlanCard({
|
||||
const isGold = tier === "Gold";
|
||||
const isPlatinum = tier === "Platinum";
|
||||
const isSilver = tier === "Silver";
|
||||
const isDevEnvironment = process.env.NODE_ENV === "development";
|
||||
const isDisabled = disabled && !isDevEnvironment;
|
||||
|
||||
const installationPrices = installations
|
||||
.map(installation => {
|
||||
@ -45,59 +47,61 @@ export function InternetPlanCard({
|
||||
|
||||
const getBorderClass = () => {
|
||||
if (isGold)
|
||||
return "border-2 border-yellow-400/50 bg-gradient-to-br from-yellow-50/80 to-amber-50/80 backdrop-blur-sm shadow-xl hover:shadow-2xl ring-2 ring-yellow-200/30";
|
||||
return "border border-yellow-200 bg-white shadow-lg hover:shadow-xl ring-1 ring-yellow-100";
|
||||
if (isPlatinum)
|
||||
return "border-2 border-indigo-400/50 bg-gradient-to-br from-indigo-50/80 to-purple-50/80 backdrop-blur-sm shadow-xl hover:shadow-2xl ring-2 ring-indigo-200/30";
|
||||
if (isSilver)
|
||||
return "border-2 border-gray-300/50 bg-gradient-to-br from-gray-50/80 to-slate-50/80 backdrop-blur-sm shadow-xl hover:shadow-2xl ring-2 ring-gray-200/30";
|
||||
return "border border-gray-200/50 bg-white/80 backdrop-blur-sm shadow-lg hover:shadow-xl";
|
||||
return "border border-indigo-200 bg-white shadow-lg hover:shadow-xl ring-1 ring-indigo-100";
|
||||
if (isSilver) return "border border-gray-200 bg-white shadow-lg hover:shadow-xl ring-1 ring-gray-100";
|
||||
return "border border-gray-200 bg-white shadow hover:shadow-lg";
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatedCard
|
||||
variant="static"
|
||||
className={`overflow-hidden flex flex-col h-full transition-all duration-500 ease-out hover:-translate-y-2 hover:scale-[1.02] ${getBorderClass()}`}
|
||||
className={`overflow-hidden flex flex-col h-full transition-all duration-300 ease-out hover:-translate-y-1 ${getBorderClass()}`}
|
||||
>
|
||||
<div className="p-6 flex flex-col flex-grow">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="p-6 flex flex-col flex-grow space-y-5">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-sm font-medium border ${
|
||||
className={`px-2.5 py-1 rounded-full text-xs font-medium border ${
|
||||
isGold
|
||||
? "bg-yellow-100 text-yellow-800 border-yellow-300"
|
||||
? "bg-yellow-50 text-yellow-700 border-yellow-200"
|
||||
: isPlatinum
|
||||
? "bg-purple-100 text-purple-800 border-purple-300"
|
||||
: "bg-gray-100 text-gray-800 border-gray-300"
|
||||
? "bg-indigo-50 text-indigo-700 border-indigo-200"
|
||||
: "bg-gray-50 text-gray-700 border-gray-200"
|
||||
}`}
|
||||
>
|
||||
{tier || "Plan"}
|
||||
</span>
|
||||
{isGold && (
|
||||
<span className="px-2 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full">
|
||||
<span className="px-2 py-0.5 text-xs font-medium text-green-700 bg-green-50 border border-green-200 rounded-full">
|
||||
Recommended
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{plan.monthlyPrice && plan.monthlyPrice > 0 && (
|
||||
<div className="text-right">
|
||||
<div className="flex items-baseline justify-end gap-1 text-2xl font-bold text-gray-900">
|
||||
<CurrencyYenIcon className="h-6 w-6" />
|
||||
<span>{plan.monthlyPrice.toLocaleString()}</span>
|
||||
<span className="text-sm text-gray-500 font-normal whitespace-nowrap">
|
||||
per month
|
||||
</span>
|
||||
<h3 className="text-lg font-semibold text-gray-900 leading-snug max-w-xs">{plan.name}</h3>
|
||||
</div>
|
||||
|
||||
{plan.monthlyPrice && plan.monthlyPrice > 0 && (
|
||||
<div className="text-right shrink-0">
|
||||
<div className="text-xs uppercase tracking-wide text-gray-500 mb-1">Monthly</div>
|
||||
<div className="text-2xl font-bold text-gray-900 leading-none">
|
||||
¥{plan.monthlyPrice.toLocaleString()}
|
||||
</div>
|
||||
{plan.oneTimePrice && plan.oneTimePrice > 0 && (
|
||||
<div className="text-xs text-gray-500 mt-1">One-time ¥{plan.oneTimePrice.toLocaleString()}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">{plan.name}</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
<p className="text-gray-600 text-sm leading-relaxed">
|
||||
{plan.catalogMetadata?.tierDescription || plan.description}
|
||||
</p>
|
||||
|
||||
<div className="mb-6 flex-grow">
|
||||
<h4 className="font-medium text-gray-900 mb-3">Your Plan Includes:</h4>
|
||||
<div className="flex-grow">
|
||||
<h4 className="font-medium text-gray-900 mb-3">Your Plan Includes</h4>
|
||||
<ul className="space-y-2 text-sm text-gray-700">
|
||||
{plan.catalogMetadata?.features && plan.catalogMetadata.features.length > 0 ? (
|
||||
plan.catalogMetadata.features.map((feature, index) => (
|
||||
@ -135,14 +139,14 @@ export function InternetPlanCard({
|
||||
|
||||
<Button
|
||||
className="w-full group"
|
||||
disabled={disabled}
|
||||
rightIcon={!disabled ? <ArrowRightIcon className="w-4 h-4" /> : undefined}
|
||||
disabled={isDisabled}
|
||||
rightIcon={!isDisabled ? <ArrowRightIcon className="w-4 h-4" /> : undefined}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
if (isDisabled) return;
|
||||
router.push(`/catalog/internet/configure?plan=${plan.sku}`);
|
||||
}}
|
||||
>
|
||||
{disabled ? disabledReason || "Not available" : "Configure Plan"}
|
||||
{isDisabled ? disabledReason || "Not available" : "Configure Plan"}
|
||||
</Button>
|
||||
</div>
|
||||
</AnimatedCard>
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
|
||||
import { PageLayout } from "@/components/templates/PageLayout";
|
||||
import { ProgressSteps } from "@/components/molecules";
|
||||
import { Button } from "@/components/atoms/button";
|
||||
import { ServerIcon, ArrowLeftIcon } from "@heroicons/react/24/outline";
|
||||
import { ServerIcon } from "@heroicons/react/24/outline";
|
||||
import { CatalogBackLink } from "@/features/catalog/components/base/CatalogBackLink";
|
||||
import type {
|
||||
InternetPlanCatalogItem,
|
||||
InternetInstallationCatalogItem,
|
||||
@ -85,18 +85,7 @@ export function InternetConfigureContainer({
|
||||
description="Set up your internet service options"
|
||||
>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="mb-6">
|
||||
<Button
|
||||
as="a"
|
||||
href="/catalog/internet"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
leftIcon={<ArrowLeftIcon className="w-4 h-4" />}
|
||||
className="text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
Back to Internet Plans
|
||||
</Button>
|
||||
</div>
|
||||
<CatalogBackLink href="/catalog/internet" label="Back to Internet Plans" />
|
||||
|
||||
{/* Plan Header */}
|
||||
<PlanHeader plan={plan} monthlyTotal={monthlyTotal} oneTimeTotal={oneTimeTotal} />
|
||||
|
||||
@ -13,12 +13,15 @@ export function ActivationForm({
|
||||
onScheduledActivationDateChange,
|
||||
errors,
|
||||
}: ActivationFormProps) {
|
||||
const sharedLabelClasses =
|
||||
"flex items-start gap-3 p-4 rounded-lg border-2 cursor-pointer transition-colors duration-200 ease-in-out";
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<label
|
||||
className={`flex items-start gap-3 p-4 rounded-lg border-2 cursor-pointer transition-all duration-300 ease-in-out hover:scale-[1.01] ${
|
||||
className={`${sharedLabelClasses} ${
|
||||
activationType === "Immediate"
|
||||
? "border-blue-500 bg-blue-50 ring-2 ring-blue-100 shadow-sm"
|
||||
? "border-blue-500 bg-blue-50 shadow-sm ring-1 ring-blue-200"
|
||||
: "border-gray-200 hover:border-blue-300 hover:bg-blue-50"
|
||||
}`}
|
||||
>
|
||||
@ -39,9 +42,9 @@ export function ActivationForm({
|
||||
</label>
|
||||
|
||||
<label
|
||||
className={`flex items-start gap-3 p-4 rounded-lg border-2 cursor-pointer transition-all duration-300 ease-in-out hover:scale-[1.01] ${
|
||||
className={`${sharedLabelClasses} ${
|
||||
activationType === "Scheduled"
|
||||
? "border-blue-500 bg-blue-50 ring-2 ring-blue-100 shadow-sm"
|
||||
? "border-blue-500 bg-blue-50 shadow-sm ring-1 ring-blue-200"
|
||||
: "border-gray-200 hover:border-blue-300 hover:bg-blue-50"
|
||||
}`}
|
||||
>
|
||||
@ -59,7 +62,12 @@ export function ActivationForm({
|
||||
Choose a specific date for activation (up to 30 days from today)
|
||||
</p>
|
||||
|
||||
{activationType === "Scheduled" && (
|
||||
<div
|
||||
className={`overflow-hidden transition-[max-height,opacity] duration-300 ease-out ${
|
||||
activationType === "Scheduled" ? "max-h-[240px] opacity-100" : "max-h-0 opacity-0"
|
||||
}`}
|
||||
aria-hidden={activationType !== "Scheduled"}
|
||||
>
|
||||
<div className="mt-3">
|
||||
<label
|
||||
htmlFor="scheduledActivationDate"
|
||||
@ -84,7 +92,7 @@ export function ActivationForm({
|
||||
requests may be processed on the next business day.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@ -9,6 +9,7 @@ import { SimTypeSelector } from "@/features/catalog/components/sim/SimTypeSelect
|
||||
import { ActivationForm } from "@/features/catalog/components/sim/ActivationForm";
|
||||
import { MnpForm } from "@/features/catalog/components/sim/MnpForm";
|
||||
import { ProgressSteps } from "@/components/molecules";
|
||||
import { CatalogBackLink } from "@/features/catalog/components/base/CatalogBackLink";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
@ -138,18 +139,7 @@ export function SimConfigureView({
|
||||
icon={<DevicePhoneMobileIcon className="h-6 w-6" />}
|
||||
>
|
||||
<div className="max-w-4xl mx-auto space-y-8">
|
||||
<div className="mb-6">
|
||||
<Button
|
||||
as="a"
|
||||
href="/catalog/sim"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
leftIcon={<ArrowLeftIcon className="w-4 h-4" />}
|
||||
className="text-gray-600 hover:text-gray-900"
|
||||
>
|
||||
Back to SIM Plans
|
||||
</Button>
|
||||
</div>
|
||||
<CatalogBackLink href="/catalog/sim" label="Back to SIM Plans" />
|
||||
|
||||
<AnimatedCard variant="static" className="p-6">
|
||||
<div className="flex justify-between items-start">
|
||||
@ -228,7 +218,12 @@ export function SimConfigureView({
|
||||
/>
|
||||
<div className="flex justify-end mt-6">
|
||||
<Button
|
||||
onClick={() => transitionToStep(2)}
|
||||
onClick={() => {
|
||||
if (simType === "eSIM" && !validate()) {
|
||||
return;
|
||||
}
|
||||
transitionToStep(2);
|
||||
}}
|
||||
rightIcon={<ArrowRightIcon className="w-4 h-4" />}
|
||||
>
|
||||
Continue to Activation
|
||||
@ -265,7 +260,12 @@ export function SimConfigureView({
|
||||
Back to SIM Type
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => transitionToStep(3)}
|
||||
onClick={() => {
|
||||
if (activationType === "Scheduled" && !validate()) {
|
||||
return;
|
||||
}
|
||||
transitionToStep(3);
|
||||
}}
|
||||
rightIcon={<ArrowRightIcon className="w-4 h-4" />}
|
||||
>
|
||||
Continue to Add-ons
|
||||
@ -349,7 +349,7 @@ export function SimConfigureView({
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (wantsMnp && !validate()) return;
|
||||
if ((wantsMnp || activationType === "Scheduled") && !validate()) return;
|
||||
transitionToStep(5);
|
||||
}}
|
||||
rightIcon={<ArrowRightIcon className="w-4 h-4" />}
|
||||
|
||||
@ -2,13 +2,7 @@
|
||||
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import { PageLayout } from "@/components/templates/PageLayout";
|
||||
import {
|
||||
WifiIcon,
|
||||
ServerIcon,
|
||||
ArrowLeftIcon,
|
||||
HomeIcon,
|
||||
BuildingOfficeIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { WifiIcon, ServerIcon, HomeIcon, BuildingOfficeIcon } from "@heroicons/react/24/outline";
|
||||
import { useInternetCatalog } from "@/features/catalog/hooks";
|
||||
import { useActiveSubscriptions } from "@/features/subscriptions/hooks/useSubscriptions";
|
||||
import type {
|
||||
@ -17,9 +11,10 @@ import type {
|
||||
} from "@customer-portal/domain/catalog";
|
||||
import { Skeleton } from "@/components/atoms/loading-skeleton";
|
||||
import { AsyncBlock } from "@/components/molecules/AsyncBlock/AsyncBlock";
|
||||
import { Button } from "@/components/atoms/button";
|
||||
import { InternetPlanCard } from "@/features/catalog/components/internet/InternetPlanCard";
|
||||
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
|
||||
import { CatalogBackLink } from "@/features/catalog/components/base/CatalogBackLink";
|
||||
import { CatalogHero } from "@/features/catalog/components/base/CatalogHero";
|
||||
|
||||
export function InternetPlansContainer() {
|
||||
const { data, isLoading, error } = useInternetCatalog();
|
||||
@ -62,6 +57,7 @@ export function InternetPlansContainer() {
|
||||
|
||||
if (isLoading || error) {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50">
|
||||
<PageLayout
|
||||
title="Internet Plans"
|
||||
description="Loading your personalized plans..."
|
||||
@ -69,10 +65,7 @@ export function InternetPlansContainer() {
|
||||
>
|
||||
<AsyncBlock isLoading={false} error={error}>
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
{/* Back */}
|
||||
<div className="mb-6">
|
||||
<div className="h-9 w-44 bg-gray-200 rounded" />
|
||||
</div>
|
||||
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||
|
||||
{/* Title + eligibility */}
|
||||
<div className="text-center mb-12">
|
||||
@ -103,60 +96,38 @@ export function InternetPlansContainer() {
|
||||
</div>
|
||||
</AsyncBlock>
|
||||
</PageLayout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50">
|
||||
<div className="min-h-screen bg-slate-50">
|
||||
<PageLayout
|
||||
title="Internet Plans"
|
||||
description="High-speed internet services for your home or business"
|
||||
icon={<WifiIcon className="h-6 w-6" />}
|
||||
>
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Enhanced Back Button */}
|
||||
<div className="mb-8">
|
||||
<Button
|
||||
as="a"
|
||||
href="/catalog"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="group bg-white/80 backdrop-blur-sm border-white/50 shadow-lg hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5"
|
||||
leftIcon={<ArrowLeftIcon className="w-4 h-4" />}
|
||||
<div className="max-w-6xl mx-auto px-4 pb-16">
|
||||
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||
|
||||
<CatalogHero
|
||||
title="Choose Your Internet Plan"
|
||||
description="High-speed fiber internet with reliable connectivity for your home or business."
|
||||
>
|
||||
Back to Services
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Enhanced Header */}
|
||||
<div className="text-center mb-16 relative">
|
||||
{/* Background decoration */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute -top-20 -right-20 w-40 h-40 bg-gradient-to-br from-blue-400/10 to-indigo-600/10 rounded-full blur-3xl"></div>
|
||||
<div className="absolute -bottom-20 -left-20 w-40 h-40 bg-gradient-to-tr from-indigo-400/10 to-purple-600/10 rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl font-bold bg-gradient-to-r from-gray-900 via-blue-900 to-indigo-900 bg-clip-text text-transparent mb-6 relative">
|
||||
Choose Your Internet Plan
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mb-8 max-w-3xl mx-auto leading-relaxed">
|
||||
High-speed fiber internet with reliable connectivity for your home or business
|
||||
</p>
|
||||
|
||||
{eligibility && (
|
||||
<div className="mt-8">
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<div
|
||||
className={`inline-flex items-center gap-3 px-8 py-4 rounded-2xl border backdrop-blur-sm shadow-lg hover:shadow-xl transition-all duration-300 ${getEligibilityColor(eligibility)}`}
|
||||
className={`inline-flex items-center gap-3 px-6 py-3 rounded-full border ${getEligibilityColor(eligibility)}`}
|
||||
>
|
||||
{getEligibilityIcon(eligibility)}
|
||||
<span className="font-semibold text-lg">Available for: {eligibility}</span>
|
||||
<span className="font-semibold">Available for: {eligibility}</span>
|
||||
</div>
|
||||
<p className="text-gray-600 mt-4 max-w-2xl mx-auto leading-relaxed">
|
||||
Plans shown are tailored to your house type and local infrastructure
|
||||
<p className="text-sm text-gray-600 text-center">
|
||||
Plans shown are tailored to your house type and local infrastructure.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CatalogHero>
|
||||
|
||||
{hasActiveInternet && (
|
||||
<AlertBanner
|
||||
@ -214,9 +185,12 @@ export function InternetPlansContainer() {
|
||||
<p className="text-gray-600 mb-6">
|
||||
We couldn't find any internet plans available for your location at this time.
|
||||
</p>
|
||||
<Button as="a" href="/catalog" leftIcon={<ArrowLeftIcon className="w-4 h-4" />}>
|
||||
Back to Services
|
||||
</Button>
|
||||
<CatalogBackLink
|
||||
href="/catalog"
|
||||
label="Back to Services"
|
||||
align="center"
|
||||
className="mt-4 mb-0"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -15,6 +15,8 @@ import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
|
||||
import { useSimCatalog } from "@/features/catalog/hooks";
|
||||
import type { SimCatalogProduct } from "@customer-portal/domain/catalog";
|
||||
import { SimPlanTypeSection } from "@/features/catalog/components/sim/SimPlanTypeSection";
|
||||
import { CatalogBackLink } from "@/features/catalog/components/base/CatalogBackLink";
|
||||
import { CatalogHero } from "@/features/catalog/components/base/CatalogHero";
|
||||
|
||||
interface PlansByType {
|
||||
DataOnly: SimCatalogProduct[];
|
||||
@ -36,16 +38,14 @@ export function SimPlansContainer() {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-slate-50">
|
||||
<PageLayout
|
||||
title="SIM Plans"
|
||||
description="Loading plans..."
|
||||
icon={<DevicePhoneMobileIcon className="h-6 w-6" />}
|
||||
>
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
{/* Back button area */}
|
||||
<div className="mb-6 flex justify-center">
|
||||
<div className="h-9 w-44 bg-gray-200 rounded" />
|
||||
</div>
|
||||
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||
|
||||
{/* Title block */}
|
||||
<div className="text-center mb-12">
|
||||
@ -93,6 +93,7 @@ export function SimPlansContainer() {
|
||||
<div className="mt-8 h-28 bg-yellow-50 border border-yellow-200 rounded-xl" />
|
||||
</div>
|
||||
</PageLayout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -132,42 +133,19 @@ export function SimPlansContainer() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-emerald-50 to-teal-50">
|
||||
<div className="min-h-screen bg-slate-50">
|
||||
<PageLayout
|
||||
title="SIM Plans"
|
||||
description="Choose your mobile plan with flexible options"
|
||||
icon={<DevicePhoneMobileIcon className="h-6 w-6" />}
|
||||
>
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
{/* Enhanced Back Button */}
|
||||
<div className="mb-8 flex justify-center">
|
||||
<Button
|
||||
as="a"
|
||||
href="/catalog"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="group bg-white/80 backdrop-blur-sm border-white/50 shadow-lg hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5"
|
||||
leftIcon={<ArrowLeftIcon className="w-4 h-4" />}
|
||||
>
|
||||
Back to Services
|
||||
</Button>
|
||||
</div>
|
||||
<div className="max-w-6xl mx-auto px-4 pb-16">
|
||||
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||
|
||||
{/* Enhanced Header */}
|
||||
<div className="text-center mb-16 relative">
|
||||
{/* Background decoration */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute -top-20 -right-20 w-40 h-40 bg-gradient-to-br from-emerald-400/10 to-teal-600/10 rounded-full blur-3xl"></div>
|
||||
<div className="absolute -bottom-20 -left-20 w-40 h-40 bg-gradient-to-tr from-teal-400/10 to-cyan-600/10 rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl font-bold bg-gradient-to-r from-gray-900 via-emerald-900 to-teal-900 bg-clip-text text-transparent mb-6 relative">
|
||||
Choose Your SIM Plan
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed">
|
||||
Wide range of data options and voice plans with both physical SIM and eSIM options.
|
||||
</p>
|
||||
</div>
|
||||
<CatalogHero
|
||||
title="Choose Your SIM Plan"
|
||||
description="Flexible mobile plans with physical SIM and eSIM options for any device."
|
||||
/>
|
||||
|
||||
{hasExistingSim && (
|
||||
<AlertBanner variant="success" title="Family Discount Applied" className="mb-8">
|
||||
@ -187,18 +165,24 @@ export function SimPlansContainer() {
|
||||
|
||||
<div className="mb-8 flex justify-center">
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="-mb-px flex space-x-8" aria-label="Tabs">
|
||||
<nav className="-mb-px flex space-x-6" aria-label="Tabs">
|
||||
<button
|
||||
onClick={() => setActiveTab("data-voice")}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm flex items-center gap-2 transition-all duration-300 ease-in-out transform hover:scale-105 ${activeTab === "data-voice" ? "border-blue-500 text-blue-600" : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"}`}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm flex items-center gap-2 transition-colors duration-200 ${
|
||||
activeTab === "data-voice"
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
<PhoneIcon
|
||||
className={`h-5 w-5 transition-transform duration-300 ${activeTab === "data-voice" ? "scale-110" : ""}`}
|
||||
/>
|
||||
<PhoneIcon className="h-5 w-5" />
|
||||
Data + SMS + Voice
|
||||
{plansByType.DataSmsVoice.length > 0 && (
|
||||
<span
|
||||
className={`bg-blue-100 text-blue-600 text-xs px-2 py-0.5 rounded-full transition-all duration-300 ${activeTab === "data-voice" ? "scale-110 bg-blue-200" : ""}`}
|
||||
className={`text-xs font-semibold px-2 py-0.5 rounded-full border ${
|
||||
activeTab === "data-voice"
|
||||
? "border-blue-100 bg-blue-50 text-blue-600"
|
||||
: "border-gray-200 text-gray-500"
|
||||
}`}
|
||||
>
|
||||
{plansByType.DataSmsVoice.length}
|
||||
</span>
|
||||
@ -206,15 +190,21 @@ export function SimPlansContainer() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("data-only")}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm flex items-center gap-2 transition-all duration-300 ease-in-out transform hover:scale-105 ${activeTab === "data-only" ? "border-purple-500 text-purple-600" : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"}`}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm flex items-center gap-2 transition-colors duration-200 ${
|
||||
activeTab === "data-only"
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
<GlobeAltIcon
|
||||
className={`h-5 w-5 transition-transform duration-300 ${activeTab === "data-only" ? "scale-110" : ""}`}
|
||||
/>
|
||||
<GlobeAltIcon className="h-5 w-5" />
|
||||
Data Only
|
||||
{plansByType.DataOnly.length > 0 && (
|
||||
<span
|
||||
className={`bg-purple-100 text-purple-600 text-xs px-2 py-0.5 rounded-full transition-all duration-300 ${activeTab === "data-only" ? "scale-110 bg-purple-200" : ""}`}
|
||||
className={`text-xs font-semibold px-2 py-0.5 rounded-full border ${
|
||||
activeTab === "data-only"
|
||||
? "border-blue-100 bg-blue-50 text-blue-600"
|
||||
: "border-gray-200 text-gray-500"
|
||||
}`}
|
||||
>
|
||||
{plansByType.DataOnly.length}
|
||||
</span>
|
||||
@ -222,15 +212,21 @@ export function SimPlansContainer() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("voice-only")}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm flex items-center gap-2 transition-all duration-300 ease-in-out transform hover:scale-105 ${activeTab === "voice-only" ? "border-orange-500 text-orange-600" : "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"}`}
|
||||
className={`py-2 px-1 border-b-2 font-medium text-sm flex items-center gap-2 transition-colors duration-200 ${
|
||||
activeTab === "voice-only"
|
||||
? "border-blue-500 text-blue-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
}`}
|
||||
>
|
||||
<PhoneIcon
|
||||
className={`h-5 w-5 transition-transform duration-300 ${activeTab === "voice-only" ? "scale-110" : ""}`}
|
||||
/>
|
||||
Voice + SMS Only
|
||||
<CheckIcon className="h-5 w-5" />
|
||||
Voice Only
|
||||
{plansByType.VoiceOnly.length > 0 && (
|
||||
<span
|
||||
className={`bg-orange-100 text-orange-600 text-xs px-2 py-0.5 rounded-full transition-all duration-300 ${activeTab === "voice-only" ? "scale-110 bg-orange-200" : ""}`}
|
||||
className={`text-xs font-semibold px-2 py-0.5 rounded-full border ${
|
||||
activeTab === "voice-only"
|
||||
? "border-blue-100 bg-blue-50 text-blue-600"
|
||||
: "border-gray-200 text-gray-500"
|
||||
}`}
|
||||
>
|
||||
{plansByType.VoiceOnly.length}
|
||||
</span>
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { PageLayout } from "@/components/templates/PageLayout";
|
||||
import { ShieldCheckIcon, ArrowLeftIcon } from "@heroicons/react/24/outline";
|
||||
import { ShieldCheckIcon } from "@heroicons/react/24/outline";
|
||||
import { useVpnCatalog } from "@/features/catalog/hooks";
|
||||
import { LoadingCard } from "@/components/atoms";
|
||||
import { AsyncBlock } from "@/components/molecules/AsyncBlock/AsyncBlock";
|
||||
import { Button } from "@/components/atoms/button";
|
||||
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
|
||||
import { VpnPlanCard } from "@/features/catalog/components/vpn/VpnPlanCard";
|
||||
import { CatalogBackLink } from "@/features/catalog/components/base/CatalogBackLink";
|
||||
import { CatalogHero } from "@/features/catalog/components/base/CatalogHero";
|
||||
|
||||
export function VpnPlansView() {
|
||||
const { data, isLoading, error } = useVpnCatalog();
|
||||
@ -16,26 +17,14 @@ export function VpnPlansView() {
|
||||
|
||||
if (isLoading || error) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-purple-50 to-indigo-50">
|
||||
<div className="min-h-screen bg-slate-50">
|
||||
<PageLayout
|
||||
title="VPN Plans"
|
||||
description="Loading plans..."
|
||||
icon={<ShieldCheckIcon className="h-6 w-6" />}
|
||||
>
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Enhanced Back Button */}
|
||||
<div className="mb-8">
|
||||
<Button
|
||||
as="a"
|
||||
href="/catalog"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="group bg-white/80 backdrop-blur-sm border-white/50 shadow-lg hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5"
|
||||
leftIcon={<ArrowLeftIcon className="w-4 h-4" />}
|
||||
>
|
||||
Back to Services
|
||||
</Button>
|
||||
</div>
|
||||
<div className="max-w-6xl mx-auto px-4">
|
||||
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||
|
||||
<AsyncBlock
|
||||
isLoading={isLoading}
|
||||
@ -56,43 +45,19 @@ export function VpnPlansView() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-purple-50 to-indigo-50">
|
||||
<div className="min-h-screen bg-slate-50">
|
||||
<PageLayout
|
||||
title="VPN Router Rental"
|
||||
description="Secure VPN router rental"
|
||||
icon={<ShieldCheckIcon className="h-6 w-6" />}
|
||||
>
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Enhanced Back Button */}
|
||||
<div className="mb-8">
|
||||
<Button
|
||||
as="a"
|
||||
href="/catalog"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="group bg-white/80 backdrop-blur-sm border-white/50 shadow-lg hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5"
|
||||
leftIcon={<ArrowLeftIcon className="w-4 h-4" />}
|
||||
>
|
||||
Back to Services
|
||||
</Button>
|
||||
</div>
|
||||
<div className="max-w-6xl mx-auto px-4 pb-16">
|
||||
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||
|
||||
{/* Enhanced Header */}
|
||||
<div className="text-center mb-16 relative">
|
||||
{/* Background decoration */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute -top-20 -right-20 w-40 h-40 bg-gradient-to-br from-purple-400/10 to-indigo-600/10 rounded-full blur-3xl"></div>
|
||||
<div className="absolute -bottom-20 -left-20 w-40 h-40 bg-gradient-to-tr from-indigo-400/10 to-violet-600/10 rounded-full blur-3xl"></div>
|
||||
</div>
|
||||
|
||||
<h1 className="text-5xl font-bold bg-gradient-to-r from-gray-900 via-purple-900 to-indigo-900 bg-clip-text text-transparent mb-6 relative">
|
||||
SonixNet VPN Router Service
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed">
|
||||
Fast and secure VPN connection to San Francisco or London for accessing geo-restricted
|
||||
content.
|
||||
</p>
|
||||
</div>
|
||||
<CatalogHero
|
||||
title="SonixNet VPN Router Service"
|
||||
description="Fast and secure VPN connections to San Francisco or London using a pre-configured router."
|
||||
/>
|
||||
|
||||
{vpnPlans.length > 0 ? (
|
||||
<div className="mb-8">
|
||||
@ -123,9 +88,12 @@ export function VpnPlansView() {
|
||||
<p className="text-gray-600 mb-6">
|
||||
We couldn't find any VPN plans available at this time.
|
||||
</p>
|
||||
<Button as="a" href="/catalog" leftIcon={<ArrowLeftIcon className="w-4 h-4" />}>
|
||||
Back to Services
|
||||
</Button>
|
||||
<CatalogBackLink
|
||||
href="/catalog"
|
||||
label="Back to Services"
|
||||
align="center"
|
||||
className="mt-4 mb-0"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user