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 { AnimatedCard } from "@/components/molecules";
|
||||||
import { Button } from "@/components/atoms/button";
|
import { Button } from "@/components/atoms/button";
|
||||||
import { CurrencyYenIcon, ArrowRightIcon } from "@heroicons/react/24/outline";
|
import { ArrowRightIcon } from "@heroicons/react/24/outline";
|
||||||
import type {
|
import type {
|
||||||
InternetPlanCatalogItem,
|
InternetPlanCatalogItem,
|
||||||
InternetInstallationCatalogItem,
|
InternetInstallationCatalogItem,
|
||||||
@ -27,6 +27,8 @@ export function InternetPlanCard({
|
|||||||
const isGold = tier === "Gold";
|
const isGold = tier === "Gold";
|
||||||
const isPlatinum = tier === "Platinum";
|
const isPlatinum = tier === "Platinum";
|
||||||
const isSilver = tier === "Silver";
|
const isSilver = tier === "Silver";
|
||||||
|
const isDevEnvironment = process.env.NODE_ENV === "development";
|
||||||
|
const isDisabled = disabled && !isDevEnvironment;
|
||||||
|
|
||||||
const installationPrices = installations
|
const installationPrices = installations
|
||||||
.map(installation => {
|
.map(installation => {
|
||||||
@ -45,59 +47,61 @@ export function InternetPlanCard({
|
|||||||
|
|
||||||
const getBorderClass = () => {
|
const getBorderClass = () => {
|
||||||
if (isGold)
|
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)
|
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";
|
return "border border-indigo-200 bg-white shadow-lg hover:shadow-xl ring-1 ring-indigo-100";
|
||||||
if (isSilver)
|
if (isSilver) return "border border-gray-200 bg-white shadow-lg hover:shadow-xl ring-1 ring-gray-100";
|
||||||
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 bg-white shadow hover:shadow-lg";
|
||||||
return "border border-gray-200/50 bg-white/80 backdrop-blur-sm shadow-lg hover:shadow-xl";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatedCard
|
<AnimatedCard
|
||||||
variant="static"
|
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="p-6 flex flex-col flex-grow space-y-5">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<span
|
<div className="flex items-center gap-2">
|
||||||
className={`px-3 py-1 rounded-full text-sm font-medium border ${
|
<span
|
||||||
isGold
|
className={`px-2.5 py-1 rounded-full text-xs font-medium border ${
|
||||||
? "bg-yellow-100 text-yellow-800 border-yellow-300"
|
isGold
|
||||||
: isPlatinum
|
? "bg-yellow-50 text-yellow-700 border-yellow-200"
|
||||||
? "bg-purple-100 text-purple-800 border-purple-300"
|
: isPlatinum
|
||||||
: "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>
|
{tier || "Plan"}
|
||||||
{isGold && (
|
|
||||||
<span className="px-2 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full">
|
|
||||||
Recommended
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
{isGold && (
|
||||||
</div>
|
<span className="px-2 py-0.5 text-xs font-medium text-green-700 bg-green-50 border border-green-200 rounded-full">
|
||||||
{plan.monthlyPrice && plan.monthlyPrice > 0 && (
|
Recommended
|
||||||
<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>
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<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>
|
</div>
|
||||||
|
{plan.oneTimePrice && plan.oneTimePrice > 0 && (
|
||||||
|
<div className="text-xs text-gray-500 mt-1">One-time ¥{plan.oneTimePrice.toLocaleString()}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">{plan.name}</h3>
|
<p className="text-gray-600 text-sm leading-relaxed">
|
||||||
<p className="text-gray-600 text-sm mb-4">
|
|
||||||
{plan.catalogMetadata?.tierDescription || plan.description}
|
{plan.catalogMetadata?.tierDescription || plan.description}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mb-6 flex-grow">
|
<div className="flex-grow">
|
||||||
<h4 className="font-medium text-gray-900 mb-3">Your Plan Includes:</h4>
|
<h4 className="font-medium text-gray-900 mb-3">Your Plan Includes</h4>
|
||||||
<ul className="space-y-2 text-sm text-gray-700">
|
<ul className="space-y-2 text-sm text-gray-700">
|
||||||
{plan.catalogMetadata?.features && plan.catalogMetadata.features.length > 0 ? (
|
{plan.catalogMetadata?.features && plan.catalogMetadata.features.length > 0 ? (
|
||||||
plan.catalogMetadata.features.map((feature, index) => (
|
plan.catalogMetadata.features.map((feature, index) => (
|
||||||
@ -135,14 +139,14 @@ export function InternetPlanCard({
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
className="w-full group"
|
className="w-full group"
|
||||||
disabled={disabled}
|
disabled={isDisabled}
|
||||||
rightIcon={!disabled ? <ArrowRightIcon className="w-4 h-4" /> : undefined}
|
rightIcon={!isDisabled ? <ArrowRightIcon className="w-4 h-4" /> : undefined}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (disabled) return;
|
if (isDisabled) return;
|
||||||
router.push(`/catalog/internet/configure?plan=${plan.sku}`);
|
router.push(`/catalog/internet/configure?plan=${plan.sku}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{disabled ? disabledReason || "Not available" : "Configure Plan"}
|
{isDisabled ? disabledReason || "Not available" : "Configure Plan"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</AnimatedCard>
|
</AnimatedCard>
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
import { PageLayout } from "@/components/templates/PageLayout";
|
import { PageLayout } from "@/components/templates/PageLayout";
|
||||||
import { ProgressSteps } from "@/components/molecules";
|
import { ProgressSteps } from "@/components/molecules";
|
||||||
import { Button } from "@/components/atoms/button";
|
import { ServerIcon } from "@heroicons/react/24/outline";
|
||||||
import { ServerIcon, ArrowLeftIcon } from "@heroicons/react/24/outline";
|
import { CatalogBackLink } from "@/features/catalog/components/base/CatalogBackLink";
|
||||||
import type {
|
import type {
|
||||||
InternetPlanCatalogItem,
|
InternetPlanCatalogItem,
|
||||||
InternetInstallationCatalogItem,
|
InternetInstallationCatalogItem,
|
||||||
@ -85,18 +85,7 @@ export function InternetConfigureContainer({
|
|||||||
description="Set up your internet service options"
|
description="Set up your internet service options"
|
||||||
>
|
>
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
<div className="mb-6">
|
<CatalogBackLink href="/catalog/internet" label="Back to Internet Plans" />
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Plan Header */}
|
{/* Plan Header */}
|
||||||
<PlanHeader plan={plan} monthlyTotal={monthlyTotal} oneTimeTotal={oneTimeTotal} />
|
<PlanHeader plan={plan} monthlyTotal={monthlyTotal} oneTimeTotal={oneTimeTotal} />
|
||||||
|
|||||||
@ -13,12 +13,15 @@ export function ActivationForm({
|
|||||||
onScheduledActivationDateChange,
|
onScheduledActivationDateChange,
|
||||||
errors,
|
errors,
|
||||||
}: ActivationFormProps) {
|
}: ActivationFormProps) {
|
||||||
|
const sharedLabelClasses =
|
||||||
|
"flex items-start gap-3 p-4 rounded-lg border-2 cursor-pointer transition-colors duration-200 ease-in-out";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<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 === "Immediate"
|
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"
|
: "border-gray-200 hover:border-blue-300 hover:bg-blue-50"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@ -39,9 +42,9 @@ export function ActivationForm({
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<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"
|
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"
|
: "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)
|
Choose a specific date for activation (up to 30 days from today)
|
||||||
</p>
|
</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">
|
<div className="mt-3">
|
||||||
<label
|
<label
|
||||||
htmlFor="scheduledActivationDate"
|
htmlFor="scheduledActivationDate"
|
||||||
@ -84,7 +92,7 @@ export function ActivationForm({
|
|||||||
requests may be processed on the next business day.
|
requests may be processed on the next business day.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { SimTypeSelector } from "@/features/catalog/components/sim/SimTypeSelect
|
|||||||
import { ActivationForm } from "@/features/catalog/components/sim/ActivationForm";
|
import { ActivationForm } from "@/features/catalog/components/sim/ActivationForm";
|
||||||
import { MnpForm } from "@/features/catalog/components/sim/MnpForm";
|
import { MnpForm } from "@/features/catalog/components/sim/MnpForm";
|
||||||
import { ProgressSteps } from "@/components/molecules";
|
import { ProgressSteps } from "@/components/molecules";
|
||||||
|
import { CatalogBackLink } from "@/features/catalog/components/base/CatalogBackLink";
|
||||||
import {
|
import {
|
||||||
ArrowLeftIcon,
|
ArrowLeftIcon,
|
||||||
ArrowRightIcon,
|
ArrowRightIcon,
|
||||||
@ -138,18 +139,7 @@ export function SimConfigureView({
|
|||||||
icon={<DevicePhoneMobileIcon className="h-6 w-6" />}
|
icon={<DevicePhoneMobileIcon className="h-6 w-6" />}
|
||||||
>
|
>
|
||||||
<div className="max-w-4xl mx-auto space-y-8">
|
<div className="max-w-4xl mx-auto space-y-8">
|
||||||
<div className="mb-6">
|
<CatalogBackLink href="/catalog/sim" label="Back to SIM Plans" />
|
||||||
<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>
|
|
||||||
|
|
||||||
<AnimatedCard variant="static" className="p-6">
|
<AnimatedCard variant="static" className="p-6">
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
@ -228,7 +218,12 @@ export function SimConfigureView({
|
|||||||
/>
|
/>
|
||||||
<div className="flex justify-end mt-6">
|
<div className="flex justify-end mt-6">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => transitionToStep(2)}
|
onClick={() => {
|
||||||
|
if (simType === "eSIM" && !validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transitionToStep(2);
|
||||||
|
}}
|
||||||
rightIcon={<ArrowRightIcon className="w-4 h-4" />}
|
rightIcon={<ArrowRightIcon className="w-4 h-4" />}
|
||||||
>
|
>
|
||||||
Continue to Activation
|
Continue to Activation
|
||||||
@ -265,7 +260,12 @@ export function SimConfigureView({
|
|||||||
Back to SIM Type
|
Back to SIM Type
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => transitionToStep(3)}
|
onClick={() => {
|
||||||
|
if (activationType === "Scheduled" && !validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transitionToStep(3);
|
||||||
|
}}
|
||||||
rightIcon={<ArrowRightIcon className="w-4 h-4" />}
|
rightIcon={<ArrowRightIcon className="w-4 h-4" />}
|
||||||
>
|
>
|
||||||
Continue to Add-ons
|
Continue to Add-ons
|
||||||
@ -349,7 +349,7 @@ export function SimConfigureView({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (wantsMnp && !validate()) return;
|
if ((wantsMnp || activationType === "Scheduled") && !validate()) return;
|
||||||
transitionToStep(5);
|
transitionToStep(5);
|
||||||
}}
|
}}
|
||||||
rightIcon={<ArrowRightIcon className="w-4 h-4" />}
|
rightIcon={<ArrowRightIcon className="w-4 h-4" />}
|
||||||
|
|||||||
@ -2,13 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect, useMemo } from "react";
|
||||||
import { PageLayout } from "@/components/templates/PageLayout";
|
import { PageLayout } from "@/components/templates/PageLayout";
|
||||||
import {
|
import { WifiIcon, ServerIcon, HomeIcon, BuildingOfficeIcon } from "@heroicons/react/24/outline";
|
||||||
WifiIcon,
|
|
||||||
ServerIcon,
|
|
||||||
ArrowLeftIcon,
|
|
||||||
HomeIcon,
|
|
||||||
BuildingOfficeIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { useInternetCatalog } from "@/features/catalog/hooks";
|
import { useInternetCatalog } from "@/features/catalog/hooks";
|
||||||
import { useActiveSubscriptions } from "@/features/subscriptions/hooks/useSubscriptions";
|
import { useActiveSubscriptions } from "@/features/subscriptions/hooks/useSubscriptions";
|
||||||
import type {
|
import type {
|
||||||
@ -17,9 +11,10 @@ import type {
|
|||||||
} from "@customer-portal/domain/catalog";
|
} from "@customer-portal/domain/catalog";
|
||||||
import { Skeleton } from "@/components/atoms/loading-skeleton";
|
import { Skeleton } from "@/components/atoms/loading-skeleton";
|
||||||
import { AsyncBlock } from "@/components/molecules/AsyncBlock/AsyncBlock";
|
import { AsyncBlock } from "@/components/molecules/AsyncBlock/AsyncBlock";
|
||||||
import { Button } from "@/components/atoms/button";
|
|
||||||
import { InternetPlanCard } from "@/features/catalog/components/internet/InternetPlanCard";
|
import { InternetPlanCard } from "@/features/catalog/components/internet/InternetPlanCard";
|
||||||
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
|
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() {
|
export function InternetPlansContainer() {
|
||||||
const { data, isLoading, error } = useInternetCatalog();
|
const { data, isLoading, error } = useInternetCatalog();
|
||||||
@ -62,101 +57,77 @@ export function InternetPlansContainer() {
|
|||||||
|
|
||||||
if (isLoading || error) {
|
if (isLoading || error) {
|
||||||
return (
|
return (
|
||||||
<PageLayout
|
<div className="min-h-screen bg-slate-50">
|
||||||
title="Internet Plans"
|
<PageLayout
|
||||||
description="Loading your personalized plans..."
|
title="Internet Plans"
|
||||||
icon={<WifiIcon className="h-6 w-6" />}
|
description="Loading your personalized plans..."
|
||||||
>
|
icon={<WifiIcon className="h-6 w-6" />}
|
||||||
<AsyncBlock isLoading={false} error={error}>
|
>
|
||||||
<div className="max-w-6xl mx-auto px-4">
|
<AsyncBlock isLoading={false} error={error}>
|
||||||
{/* Back */}
|
<div className="max-w-6xl mx-auto px-4">
|
||||||
<div className="mb-6">
|
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||||
<div className="h-9 w-44 bg-gray-200 rounded" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Title + eligibility */}
|
{/* Title + eligibility */}
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<div className="h-10 w-96 bg-gray-200 rounded mx-auto mb-4" />
|
<div className="h-10 w-96 bg-gray-200 rounded mx-auto mb-4" />
|
||||||
<div className="mt-6 inline-flex items-center gap-2 px-6 py-3 rounded-2xl border">
|
<div className="mt-6 inline-flex items-center gap-2 px-6 py-3 rounded-2xl border">
|
||||||
<div className="h-5 w-5 bg-gray-200 rounded" />
|
<div className="h-5 w-5 bg-gray-200 rounded" />
|
||||||
<div className="h-4 w-56 bg-gray-200 rounded" />
|
<div className="h-4 w-56 bg-gray-200 rounded" />
|
||||||
</div>
|
|
||||||
<div className="h-4 w-[32rem] max-w-full bg-gray-200 rounded mx-auto mt-2" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Active internet warning slot */}
|
|
||||||
<div className="mb-8 h-20 bg-yellow-50 border border-yellow-200 rounded-xl" />
|
|
||||||
|
|
||||||
{/* Plans grid */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
||||||
{Array.from({ length: 6 }).map((_, i) => (
|
|
||||||
<div key={i} className="bg-white rounded-xl border border-gray-200 p-6 space-y-3">
|
|
||||||
<Skeleton className="h-6 w-1/2" />
|
|
||||||
<Skeleton className="h-4 w-2/3" />
|
|
||||||
<Skeleton className="h-10 w-full" />
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
<div className="h-4 w-[32rem] max-w-full bg-gray-200 rounded mx-auto mt-2" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Important Notes */}
|
{/* Active internet warning slot */}
|
||||||
<div className="mt-12 h-24 bg-blue-50 border border-blue-200 rounded-xl" />
|
<div className="mb-8 h-20 bg-yellow-50 border border-yellow-200 rounded-xl" />
|
||||||
</div>
|
|
||||||
</AsyncBlock>
|
{/* Plans grid */}
|
||||||
</PageLayout>
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<div key={i} className="bg-white rounded-xl border border-gray-200 p-6 space-y-3">
|
||||||
|
<Skeleton className="h-6 w-1/2" />
|
||||||
|
<Skeleton className="h-4 w-2/3" />
|
||||||
|
<Skeleton className="h-10 w-full" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Important Notes */}
|
||||||
|
<div className="mt-12 h-24 bg-blue-50 border border-blue-200 rounded-xl" />
|
||||||
|
</div>
|
||||||
|
</AsyncBlock>
|
||||||
|
</PageLayout>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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
|
<PageLayout
|
||||||
title="Internet Plans"
|
title="Internet Plans"
|
||||||
description="High-speed internet services for your home or business"
|
description="High-speed internet services for your home or business"
|
||||||
icon={<WifiIcon className="h-6 w-6" />}
|
icon={<WifiIcon className="h-6 w-6" />}
|
||||||
>
|
>
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto px-4 pb-16">
|
||||||
{/* Enhanced Back Button */}
|
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* 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>
|
|
||||||
|
|
||||||
|
<CatalogHero
|
||||||
|
title="Choose Your Internet Plan"
|
||||||
|
description="High-speed fiber internet with reliable connectivity for your home or business."
|
||||||
|
>
|
||||||
{eligibility && (
|
{eligibility && (
|
||||||
<div className="mt-8">
|
<div className="flex flex-col items-center gap-3">
|
||||||
<div
|
<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)}
|
{getEligibilityIcon(eligibility)}
|
||||||
<span className="font-semibold text-lg">Available for: {eligibility}</span>
|
<span className="font-semibold">Available for: {eligibility}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 mt-4 max-w-2xl mx-auto leading-relaxed">
|
<p className="text-sm text-gray-600 text-center">
|
||||||
Plans shown are tailored to your house type and local infrastructure
|
Plans shown are tailored to your house type and local infrastructure.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</CatalogHero>
|
||||||
|
|
||||||
{hasActiveInternet && (
|
{hasActiveInternet && (
|
||||||
<AlertBanner
|
<AlertBanner
|
||||||
@ -214,9 +185,12 @@ export function InternetPlansContainer() {
|
|||||||
<p className="text-gray-600 mb-6">
|
<p className="text-gray-600 mb-6">
|
||||||
We couldn't find any internet plans available for your location at this time.
|
We couldn't find any internet plans available for your location at this time.
|
||||||
</p>
|
</p>
|
||||||
<Button as="a" href="/catalog" leftIcon={<ArrowLeftIcon className="w-4 h-4" />}>
|
<CatalogBackLink
|
||||||
Back to Services
|
href="/catalog"
|
||||||
</Button>
|
label="Back to Services"
|
||||||
|
align="center"
|
||||||
|
className="mt-4 mb-0"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -15,6 +15,8 @@ import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
|
|||||||
import { useSimCatalog } from "@/features/catalog/hooks";
|
import { useSimCatalog } from "@/features/catalog/hooks";
|
||||||
import type { SimCatalogProduct } from "@customer-portal/domain/catalog";
|
import type { SimCatalogProduct } from "@customer-portal/domain/catalog";
|
||||||
import { SimPlanTypeSection } from "@/features/catalog/components/sim/SimPlanTypeSection";
|
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 {
|
interface PlansByType {
|
||||||
DataOnly: SimCatalogProduct[];
|
DataOnly: SimCatalogProduct[];
|
||||||
@ -36,63 +38,62 @@ export function SimPlansContainer() {
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<PageLayout
|
<div className="min-h-screen bg-slate-50">
|
||||||
title="SIM Plans"
|
<PageLayout
|
||||||
description="Loading plans..."
|
title="SIM Plans"
|
||||||
icon={<DevicePhoneMobileIcon className="h-6 w-6" />}
|
description="Loading plans..."
|
||||||
>
|
icon={<DevicePhoneMobileIcon className="h-6 w-6" />}
|
||||||
<div className="max-w-6xl mx-auto px-4">
|
>
|
||||||
{/* Back button area */}
|
<div className="max-w-6xl mx-auto px-4">
|
||||||
<div className="mb-6 flex justify-center">
|
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||||
<div className="h-9 w-44 bg-gray-200 rounded" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Title block */}
|
{/* Title block */}
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<div className="h-10 w-80 bg-gray-200 rounded mx-auto mb-4" />
|
<div className="h-10 w-80 bg-gray-200 rounded mx-auto mb-4" />
|
||||||
<div className="h-6 w-[36rem] max-w-full bg-gray-200 rounded mx-auto" />
|
<div className="h-6 w-[36rem] max-w-full bg-gray-200 rounded mx-auto" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Family discount banner slot */}
|
{/* Family discount banner slot */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="h-20 w-full bg-green-50 border border-green-200 rounded-xl" />
|
<div className="h-20 w-full bg-green-50 border border-green-200 rounded-xl" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tabs */}
|
{/* Tabs */}
|
||||||
<div className="mb-8 flex justify-center">
|
<div className="mb-8 flex justify-center">
|
||||||
<div className="h-10 w-[32rem] max-w-full bg-gray-200 rounded" />
|
<div className="h-10 w-[32rem] max-w-full bg-gray-200 rounded" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Plans grid */}
|
{/* Plans grid */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
||||||
{Array.from({ length: 6 }).map((_, i) => (
|
|
||||||
<div key={i} className="bg-white rounded-xl border border-gray-200 p-6 space-y-3">
|
|
||||||
<Skeleton className="h-6 w-1/2" />
|
|
||||||
<Skeleton className="h-4 w-2/3" />
|
|
||||||
<Skeleton className="h-10 w-full" />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Terms section */}
|
|
||||||
<div className="mt-8 bg-gray-50 rounded-2xl p-8">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{Array.from({ length: 6 }).map((_, i) => (
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
<div key={i} className="flex items-start gap-3">
|
<div key={i} className="bg-white rounded-xl border border-gray-200 p-6 space-y-3">
|
||||||
<div className="h-5 w-5 bg-gray-200 rounded" />
|
<Skeleton className="h-6 w-1/2" />
|
||||||
<div className="space-y-2 flex-1">
|
<Skeleton className="h-4 w-2/3" />
|
||||||
<Skeleton className="h-4 w-40" />
|
<Skeleton className="h-10 w-full" />
|
||||||
<Skeleton className="h-3 w-56" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Important terms banner */}
|
{/* Terms section */}
|
||||||
<div className="mt-8 h-28 bg-yellow-50 border border-yellow-200 rounded-xl" />
|
<div className="mt-8 bg-gray-50 rounded-2xl p-8">
|
||||||
</div>
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
</PageLayout>
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<div key={i} className="flex items-start gap-3">
|
||||||
|
<div className="h-5 w-5 bg-gray-200 rounded" />
|
||||||
|
<div className="space-y-2 flex-1">
|
||||||
|
<Skeleton className="h-4 w-40" />
|
||||||
|
<Skeleton className="h-3 w-56" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Important terms banner */}
|
||||||
|
<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 (
|
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
|
<PageLayout
|
||||||
title="SIM Plans"
|
title="SIM Plans"
|
||||||
description="Choose your mobile plan with flexible options"
|
description="Choose your mobile plan with flexible options"
|
||||||
icon={<DevicePhoneMobileIcon className="h-6 w-6" />}
|
icon={<DevicePhoneMobileIcon className="h-6 w-6" />}
|
||||||
>
|
>
|
||||||
<div className="max-w-6xl mx-auto px-4">
|
<div className="max-w-6xl mx-auto px-4 pb-16">
|
||||||
{/* Enhanced Back Button */}
|
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Enhanced Header */}
|
<CatalogHero
|
||||||
<div className="text-center mb-16 relative">
|
title="Choose Your SIM Plan"
|
||||||
{/* Background decoration */}
|
description="Flexible mobile plans with physical SIM and eSIM options for any device."
|
||||||
<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>
|
|
||||||
|
|
||||||
{hasExistingSim && (
|
{hasExistingSim && (
|
||||||
<AlertBanner variant="success" title="Family Discount Applied" className="mb-8">
|
<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="mb-8 flex justify-center">
|
||||||
<div className="border-b border-gray-200">
|
<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
|
<button
|
||||||
onClick={() => setActiveTab("data-voice")}
|
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
|
<PhoneIcon className="h-5 w-5" />
|
||||||
className={`h-5 w-5 transition-transform duration-300 ${activeTab === "data-voice" ? "scale-110" : ""}`}
|
|
||||||
/>
|
|
||||||
Data + SMS + Voice
|
Data + SMS + Voice
|
||||||
{plansByType.DataSmsVoice.length > 0 && (
|
{plansByType.DataSmsVoice.length > 0 && (
|
||||||
<span
|
<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}
|
{plansByType.DataSmsVoice.length}
|
||||||
</span>
|
</span>
|
||||||
@ -206,15 +190,21 @@ export function SimPlansContainer() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("data-only")}
|
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
|
<GlobeAltIcon className="h-5 w-5" />
|
||||||
className={`h-5 w-5 transition-transform duration-300 ${activeTab === "data-only" ? "scale-110" : ""}`}
|
|
||||||
/>
|
|
||||||
Data Only
|
Data Only
|
||||||
{plansByType.DataOnly.length > 0 && (
|
{plansByType.DataOnly.length > 0 && (
|
||||||
<span
|
<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}
|
{plansByType.DataOnly.length}
|
||||||
</span>
|
</span>
|
||||||
@ -222,15 +212,21 @@ export function SimPlansContainer() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("voice-only")}
|
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
|
<CheckIcon className="h-5 w-5" />
|
||||||
className={`h-5 w-5 transition-transform duration-300 ${activeTab === "voice-only" ? "scale-110" : ""}`}
|
Voice Only
|
||||||
/>
|
|
||||||
Voice + SMS Only
|
|
||||||
{plansByType.VoiceOnly.length > 0 && (
|
{plansByType.VoiceOnly.length > 0 && (
|
||||||
<span
|
<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}
|
{plansByType.VoiceOnly.length}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { PageLayout } from "@/components/templates/PageLayout";
|
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 { useVpnCatalog } from "@/features/catalog/hooks";
|
||||||
import { LoadingCard } from "@/components/atoms";
|
import { LoadingCard } from "@/components/atoms";
|
||||||
import { AsyncBlock } from "@/components/molecules/AsyncBlock/AsyncBlock";
|
import { AsyncBlock } from "@/components/molecules/AsyncBlock/AsyncBlock";
|
||||||
import { Button } from "@/components/atoms/button";
|
|
||||||
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
|
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
|
||||||
import { VpnPlanCard } from "@/features/catalog/components/vpn/VpnPlanCard";
|
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() {
|
export function VpnPlansView() {
|
||||||
const { data, isLoading, error } = useVpnCatalog();
|
const { data, isLoading, error } = useVpnCatalog();
|
||||||
@ -16,26 +17,14 @@ export function VpnPlansView() {
|
|||||||
|
|
||||||
if (isLoading || error) {
|
if (isLoading || error) {
|
||||||
return (
|
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
|
<PageLayout
|
||||||
title="VPN Plans"
|
title="VPN Plans"
|
||||||
description="Loading plans..."
|
description="Loading plans..."
|
||||||
icon={<ShieldCheckIcon className="h-6 w-6" />}
|
icon={<ShieldCheckIcon className="h-6 w-6" />}
|
||||||
>
|
>
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto px-4">
|
||||||
{/* Enhanced Back Button */}
|
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||||
<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>
|
|
||||||
|
|
||||||
<AsyncBlock
|
<AsyncBlock
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
@ -56,43 +45,19 @@ export function VpnPlansView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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
|
<PageLayout
|
||||||
title="VPN Router Rental"
|
title="VPN Router Rental"
|
||||||
description="Secure VPN router rental"
|
description="Secure VPN router rental"
|
||||||
icon={<ShieldCheckIcon className="h-6 w-6" />}
|
icon={<ShieldCheckIcon className="h-6 w-6" />}
|
||||||
>
|
>
|
||||||
<div className="max-w-6xl mx-auto">
|
<div className="max-w-6xl mx-auto px-4 pb-16">
|
||||||
{/* Enhanced Back Button */}
|
<CatalogBackLink href="/catalog" label="Back to Services" />
|
||||||
<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>
|
|
||||||
|
|
||||||
{/* Enhanced Header */}
|
<CatalogHero
|
||||||
<div className="text-center mb-16 relative">
|
title="SonixNet VPN Router Service"
|
||||||
{/* Background decoration */}
|
description="Fast and secure VPN connections to San Francisco or London using a pre-configured router."
|
||||||
<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>
|
|
||||||
|
|
||||||
{vpnPlans.length > 0 ? (
|
{vpnPlans.length > 0 ? (
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
@ -123,9 +88,12 @@ export function VpnPlansView() {
|
|||||||
<p className="text-gray-600 mb-6">
|
<p className="text-gray-600 mb-6">
|
||||||
We couldn't find any VPN plans available at this time.
|
We couldn't find any VPN plans available at this time.
|
||||||
</p>
|
</p>
|
||||||
<Button as="a" href="/catalog" leftIcon={<ArrowLeftIcon className="w-4 h-4" />}>
|
<CatalogBackLink
|
||||||
Back to Services
|
href="/catalog"
|
||||||
</Button>
|
label="Back to Services"
|
||||||
|
align="center"
|
||||||
|
className="mt-4 mb-0"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user