From df017d520f622638cdb1ed1a8feb0283d7c08193 Mon Sep 17 00:00:00 2001 From: Temuulen Ankhbayar Date: Sat, 7 Feb 2026 14:53:07 +0900 Subject: [PATCH] fix: resolve SIM management modal, plan change, and voice feature issues - Fix Tailwind v4 modal stacking bug by adding relative z-10 to modal content divs (CancellationFlow, ChangePlanModal, TopUpModal, SimActions) - Add test mode for immediate plan changes (SIM_BILLING_TEST_MODE) instead of scheduling for 1st of next month - Bypass rate limiter spacing/cancellation checks in test mode - Hide voice feature toggles for data-only SIMs using hasVoice flag - Guard BFF voice feature updates to reject early for data-only SIMs - Fix Freebit retry logic to not retry business errors (e.g. resultCode 260) - Add user-friendly error message for resultCode 260 (voice not active) - Update plan change page text to reflect test mode behavior Co-Authored-By: Claude Opus 4.6 --- .../freebit/services/freebit-error.service.ts | 26 +- .../services/freebit-rate-limiter.service.ts | 20 +- .../services/mutations/sim-plan.service.ts | 44 +- .../(site)/services/business/page.tsx | 564 +++++++++++++++--- .../services/onsite/OnsiteSupportContent.tsx | 551 +++++++++++++---- .../common/ServicesOverviewContent.tsx | 414 +++++++++---- .../CancellationFlow/CancellationFlow.tsx | 2 +- .../components/sim/ChangePlanModal.tsx | 2 +- .../components/sim/SimActions.tsx | 2 +- .../components/sim/SimDetailsCard.tsx | 74 ++- .../components/sim/SimManagementSection.tsx | 61 +- .../components/sim/TopUpModal.tsx | 2 +- .../subscriptions/views/SimChangePlan.tsx | 28 +- 13 files changed, 1392 insertions(+), 398 deletions(-) diff --git a/apps/bff/src/integrations/freebit/services/freebit-error.service.ts b/apps/bff/src/integrations/freebit/services/freebit-error.service.ts index 1bf9559a..feb7f298 100644 --- a/apps/bff/src/integrations/freebit/services/freebit-error.service.ts +++ b/apps/bff/src/integrations/freebit/services/freebit-error.service.ts @@ -49,13 +49,27 @@ export class FreebitError extends Error { } /** - * Check if error is retryable + * Check if error is retryable. + * + * IMPORTANT: Freebit uses statusCode "500" for business logic errors (e.g. 210, 211, 260), + * not just HTTP 500 server errors. Only resultCode 900 (unexpected error) is truly retryable. + * We should NOT retry known business errors even though their statusCode is "500". */ isRetryable(): boolean { - const retryableCodes = ["500", "502", "503", "504", "408", "429"]; + // Freebit-specific: only retry unexpected errors (900) or actual HTTP transport errors + if (this.resultCode === "900") { + return true; + } + + // If we have a Freebit resultCode, it's a business error — don't retry + if (this.resultCode && this.resultCode !== "900") { + return false; + } + + // For non-Freebit errors (HTTP transport failures), use standard retry logic + const retryableHttpCodes = ["500", "502", "503", "504", "408", "429"]; return ( - retryableCodes.includes(String(this.resultCode)) || - retryableCodes.includes(String(this.statusCode)) || + retryableHttpCodes.includes(String(this.statusCode)) || this.message.toLowerCase().includes("timeout") || this.message.toLowerCase().includes("network") ); @@ -94,6 +108,10 @@ export class FreebitError extends Error { return "Plan change failed. This may be due to: (1) Account has existing scheduled operations, (2) Invalid plan code for this account, (3) Account restrictions. Please check the Freebit Partner Tools for account status or contact support."; } + if (this.resultCode === "260" || this.statusCode === "260") { + return "Voice options cannot be changed because voice service is not active on this account. Voice options must first be registered via the initial setup process."; + } + if (this.resultCode === "381" || this.statusCode === "381") { return "Network type change rejected. The current plan does not allow switching to the requested contract line. Adjust the plan first or contact support."; } diff --git a/apps/bff/src/integrations/freebit/services/freebit-rate-limiter.service.ts b/apps/bff/src/integrations/freebit/services/freebit-rate-limiter.service.ts index 0c1fa625..dc9288b9 100644 --- a/apps/bff/src/integrations/freebit/services/freebit-rate-limiter.service.ts +++ b/apps/bff/src/integrations/freebit/services/freebit-rate-limiter.service.ts @@ -1,4 +1,5 @@ import { Injectable, Inject, BadRequestException } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; import { Logger } from "nestjs-pino"; import type { Redis } from "ioredis"; import { randomUUID } from "crypto"; @@ -32,10 +33,20 @@ export class FreebitRateLimiterService { private readonly windowMs = 30 * 60 * 1000; // 30 minute window between operations private readonly lockTtlMs = Math.min(this.windowMs, 10 * 60 * 1000); + private readonly testMode: boolean; + constructor( @Inject("REDIS_CLIENT") private readonly redis: Redis, + private readonly configService: ConfigService, @Inject(Logger) private readonly logger: Logger - ) {} + ) { + this.testMode = this.configService.get("SIM_BILLING_TEST_MODE") === "true"; + if (this.testMode) { + this.logger.warn( + "Freebit rate limiter is in TEST MODE - spacing checks and cancellation blocks are bypassed" + ); + } + } /** * Cleanup stale entries from operationTimestamps to prevent memory leak. @@ -174,6 +185,13 @@ export class FreebitRateLimiterService { * Throws BadRequestException if the operation violates timing rules. */ async assertOperationSpacing(account: string, op: OperationType): Promise { + if (this.testMode) { + this.logger.debug( + `TEST MODE: Skipping operation spacing check for ${op} on account ${account}` + ); + return; + } + const now = Date.now(); const entry = await this.getOperationWindow(account); diff --git a/apps/bff/src/modules/subscriptions/sim-management/services/mutations/sim-plan.service.ts b/apps/bff/src/modules/subscriptions/sim-management/services/mutations/sim-plan.service.ts index 7d1b9af6..429d08ea 100644 --- a/apps/bff/src/modules/subscriptions/sim-management/services/mutations/sim-plan.service.ts +++ b/apps/bff/src/modules/subscriptions/sim-management/services/mutations/sim-plan.service.ts @@ -34,6 +34,8 @@ const FREEBIT_PLAN_CODE_TO_SKU: Record = Object.fromEntries( @Injectable() export class SimPlanService { + private readonly testMode: boolean; + // eslint-disable-next-line max-params -- NestJS dependency injection requires all services to be injected via constructor constructor( private readonly freebitService: FreebitFacade, @@ -44,7 +46,14 @@ export class SimPlanService { private readonly simCatalog: SimServicesService, private readonly configService: ConfigService, @Inject(Logger) private readonly logger: Logger - ) {} + ) { + this.testMode = this.configService.get("SIM_BILLING_TEST_MODE") === "true"; + if (this.testMode) { + this.logger.warn( + "SIM Plan service is in TEST MODE - plan changes will take effect immediately" + ); + } + } private get freebitBaseUrl(): string { return this.configService.get("FREEBIT_BASE_URL") || "https://i1.mvno.net/emptool/api"; @@ -194,13 +203,22 @@ export class SimPlanService { throw new BadRequestException("Invalid plan code"); } - // Always schedule for 1st of following month - const nextMonth = new Date(); - nextMonth.setMonth(nextMonth.getMonth() + 1); - nextMonth.setDate(1); - const year = nextMonth.getFullYear(); - const month = String(nextMonth.getMonth() + 1).padStart(2, "0"); - const scheduledAt = `${year}${month}01`; + // In test mode, schedule for today (immediate effect); otherwise 1st of following month + let scheduledAt: string; + if (this.testMode) { + const today = new Date(); + const year = today.getFullYear(); + const month = String(today.getMonth() + 1).padStart(2, "0"); + const day = String(today.getDate()).padStart(2, "0"); + scheduledAt = `${year}${month}${day}`; + } else { + const nextMonth = new Date(); + nextMonth.setMonth(nextMonth.getMonth() + 1); + nextMonth.setDate(1); + const year = nextMonth.getFullYear(); + const month = String(nextMonth.getMonth() + 1).padStart(2, "0"); + scheduledAt = `${year}${month}01`; + } this.logger.log("Submitting SIM plan change request (full)", { userId, @@ -288,6 +306,16 @@ export class SimPlanService { const hasVoiceChanges = this.hasVoiceFeatureChanges(request); const hasContractChange = typeof request.networkType === "string"; + // Guard: reject voice feature changes for data-only SIMs (voice not active) + if (hasVoiceChanges) { + const simDetails = await this.freebitService.getSimDetails(account); + if (!simDetails.hasVoice) { + throw new BadRequestException( + "Voice features cannot be changed because voice service is not active on this data-only SIM." + ); + } + } + if (hasVoiceChanges && hasContractChange) { await this.applyVoiceThenContractChanges(account, request, userId, subscriptionId); } else { diff --git a/apps/portal/src/app/(public)/(site)/services/business/page.tsx b/apps/portal/src/app/(public)/(site)/services/business/page.tsx index 6180276e..5468c59f 100644 --- a/apps/portal/src/app/(public)/(site)/services/business/page.tsx +++ b/apps/portal/src/app/(public)/(site)/services/business/page.tsx @@ -1,109 +1,513 @@ import type { Metadata } from "next"; import { Button } from "@/components/atoms"; -import { Server, Monitor, Wrench, Globe } from "lucide-react"; +import Link from "next/link"; export const metadata: Metadata = { title: "IT Solutions for International Businesses in Japan | Assist Solutions", description: - "Enterprise IT for foreign companies in Japan. Dedicated internet, office networks, data center hosting, all with bilingual support. We understand international business needs.", + "Enterprise IT for foreign companies in Japan. Dedicated internet, office networks, data center hosting, web construction & maintenance, all with bilingual support.", keywords: [ "IT for foreign companies Japan", "international business IT Tokyo", "bilingual IT support Japan", "office network setup foreigners", "enterprise IT English support", + "web design Japan English", + "website maintenance Tokyo", + "bilingual web development", ], openGraph: { title: "Business IT for International Companies - Assist Solutions", description: - "Enterprise IT with bilingual support. Dedicated internet, office networks, and data center services for foreign companies in Japan.", + "Enterprise IT with bilingual support. Dedicated internet, office networks, web construction, and data center services for foreign companies in Japan.", type: "website", }, }; -export default function BusinessSolutionsPage() { +// Abstract tech illustration components +function NetworkNodesIllustration({ className }: { className?: string }) { return ( -
- {/* Header */} -
-

- IT for International Businesses -

-

- Running an international company in Japan? We provide enterprise IT with bilingual - support, so your team can focus on business, not navigating Japanese tech providers. -

+ + + + + + + + + + + + + + + + + + ); +} + +function GearIllustration({ className }: { className?: string }) { + return ( + + + + + + + + ); +} + +function ShieldIllustration({ className }: { className?: string }) { + return ( + + + + + + ); +} + +function CodeIllustration({ className }: { className?: string }) { + return ( + + + + + + + + + + + ); +} + +function ServerIllustration({ className }: { className?: string }) { + return ( + + + + + + + + + + + + + ); +} + +// Service card component +interface ServiceCardProps { + category: string; + title: string; + description: string; + bgColor: string; + textColor: string; + accentColor: string; + illustration: React.ReactNode; + href: string; +} + +function ServiceCard({ + category, + title, + description, + bgColor, + textColor, + accentColor, + illustration, + href, +}: ServiceCardProps) { + return ( +
+ {/* Tech illustration overlay */} +
+ {illustration}
-
- {/* Office LAN Setup */} -
-
- -
-

Office LAN Setup

-

- Setting up a new office or upgrading your network? We handle everything in English. from - planning to installation. Cable runs, switches, routers, and firewalls configured by - bilingual technicians who understand international business needs. -

-
+ {/* Content */} +
+ {/* Category label */} + + {category} + - {/* Onsite & Remote Tech Support */} -
-
- -
-

Onsite & Remote Tech Support

-

- IT issues don't wait, and neither do we. Our English-speaking technicians provide - fast onsite and remote support for your business. Network problems, hardware issues, - software setup. We keep your operations running smoothly. -

-
- - {/* Dedicated Internet Access (DIA) */} -
-
- -
-

- Dedicated Internet Access (DIA) -

-

- Need guaranteed bandwidth for your business? Our Dedicated Internet Access provides - enterprise-grade connectivity with SLA guarantees. Perfect for companies requiring - reliable, high-capacity connections with English contracts and support. -

-
- - {/* Data Center Service */} -
-
- -
-

Data Center Service

-

- Host your infrastructure in world-class Tokyo data centers (Equinix, GDC Gotenyama). We - provide colocation and managed services with English support, making it easy for - international companies to establish reliable IT infrastructure in Japan. -

-
-
- - {/* CTA */} -
-

- Let's Talk About Your IT Needs + {/* Title */} +

+ {title}

-

- Running an international business in Japan comes with unique challenges. We've been - helping foreign companies navigate Japanese IT for over 20 years. Let's discuss how - we can support your operations. + + {/* Description */} +

+ {description}

- + + {/* Spacer */} +
+ + {/* CTA */} + + Contact Us + + + + +
+
+ ); +} + +export default function BusinessSolutionsPage() { + const services: ServiceCardProps[] = [ + { + category: "Infrastructure", + title: "Office LAN Setup", + description: + "Complete network infrastructure from planning to installation. Routers, switches, and firewalls configured by bilingual technicians.", + bgColor: "bg-[#1E3A5F]", + textColor: "text-white", + accentColor: "text-blue-300", + illustration: , + href: "/contact", + }, + { + category: "Support", + title: "Onsite & Remote Tech Support", + description: + "Fast response from English-speaking technicians. Network issues, hardware problems, software setup — we keep you running.", + bgColor: "bg-white", + textColor: "text-gray-900", + accentColor: "text-gray-400", + illustration: , + href: "/contact", + }, + { + category: "Connectivity", + title: "Dedicated Internet Access", + description: + "Enterprise-grade connectivity with SLA guarantees. Reliable, high-capacity connections with English contracts and support.", + bgColor: "bg-[#F5F0E8]", + textColor: "text-gray-900", + accentColor: "text-amber-700", + illustration: , + href: "/contact", + }, + { + category: "Hosting", + title: "Data Center Service", + description: + "World-class Tokyo data centers including Equinix and GDC Gotenyama. Colocation and managed services with full English support.", + bgColor: "bg-white", + textColor: "text-gray-900", + accentColor: "text-gray-400", + illustration: , + href: "/contact", + }, + { + category: "Digital", + title: "Web Construction & Maintenance", + description: + "Modern, responsive websites with bilingual support. From initial design to ongoing maintenance — your complete web partner.", + bgColor: "bg-[#1E3A5F]", + textColor: "text-white", + accentColor: "text-blue-300", + illustration: , + href: "/contact", + }, + ]; + + return ( +
+ {/* Hero Section */} +
+
+
+

+ Enterprise Solutions +

+

+ IT for International +
+ Businesses in Japan +

+

+ Running an international company in Japan? We provide enterprise IT with bilingual + support, so your team can focus on business — not navigating Japanese tech providers. +

+
+
+
+ + {/* Services Card Grid */} +
+ {/* Desktop: Horizontal scroll layout */} +
+
+ {services.map((service, index) => ( + + ))} +
+
+ + {/* Tablet: 2-column grid */} +
+ {services.map((service, index) => ( + + ))} +
+ + {/* Mobile: Single column */} +
+ {services.map((service, index) => ( + + ))} +
+
+ + {/* Bottom CTA Section */} +
+
+
+
+

+ Let's Talk About Your IT Needs +

+

+ We've been helping foreign companies navigate Japanese IT for over 20 years. + Let's discuss how we can support your operations. +

+
+ +
+
); diff --git a/apps/portal/src/app/(public)/(site)/services/onsite/OnsiteSupportContent.tsx b/apps/portal/src/app/(public)/(site)/services/onsite/OnsiteSupportContent.tsx index 920c6951..88b4ceaa 100644 --- a/apps/portal/src/app/(public)/(site)/services/onsite/OnsiteSupportContent.tsx +++ b/apps/portal/src/app/(public)/(site)/services/onsite/OnsiteSupportContent.tsx @@ -1,146 +1,427 @@ "use client"; import { useState } from "react"; +import Link from "next/link"; import { Button } from "@/components/atoms"; -import { Users, Monitor, Tv, Headset, ChevronDown } from "lucide-react"; +import { + Monitor, + Tv, + Headset, + ChevronDown, + Wrench, + Wifi, + Printer, + HardDrive, + CheckCircle2, + Calendar, + UserCheck, + ArrowRight, + Clock, + Shield, +} from "lucide-react"; + +// Service types for the pricing cards +const services = [ + { + id: "onsite", + icon: Monitor, + title: "Onsite Support", + subtitle: "Home & Office Visits", + description: + "We come to your location for hands-on help with networks, computers, and devices.", + price: "15,000", + features: [ + "Home or office visit", + "Network setup & troubleshooting", + "Device configuration", + "Same-week scheduling", + ], + }, + { + id: "remote", + icon: Headset, + title: "Remote Support", + subtitle: "Quick Online Help", + description: "We connect securely to your device to diagnose and fix issues remotely.", + price: "5,000", + popular: true, + features: [ + "Secure remote connection", + "Quick turnaround", + "Software troubleshooting", + "No waiting for visit", + ], + }, + { + id: "tv", + icon: Tv, + title: "TV & Streaming Setup", + subtitle: "Entertainment Systems", + description: "Setup and configuration of smart TVs, streaming devices, and home entertainment.", + price: "15,000", + features: [ + "Smart TV setup", + "Streaming device config", + "Apple TV, Fire TV, etc.", + "Content access help", + ], + }, +]; + +// What we help with +const helpItems = [ + { icon: Wifi, label: "Wi-Fi & Routers" }, + { icon: Monitor, label: "Computers" }, + { icon: Printer, label: "Printers" }, + { icon: Tv, label: "Smart TVs" }, + { icon: HardDrive, label: "Storage & Backup" }, + { icon: Wrench, label: "General Tech" }, +]; + +// How it works steps +const steps = [ + { + number: "1", + icon: Calendar, + title: "Book a Visit", + description: + "Contact us to schedule a time that works for you. Same-week appointments available.", + }, + { + number: "2", + icon: UserCheck, + title: "We Come to You", + description: + "An English-speaking technician arrives at your home or office at the scheduled time.", + }, + { + number: "3", + icon: CheckCircle2, + title: "Problem Solved", + description: "We fix the issue and explain everything clearly so you understand what was done.", + }, +]; export function OnsiteSupportContent() { return ( -
- {/* Header */} -
-

- Tech Help in English, At Your Door -

-

- Need help with your router, computer, or home network? Our English-speaking technicians - come to your home or office to solve tech problems, explained in a language you - understand. -

-
+
+ {/* Hero Section */} +
+ {/* Dot grid pattern */} +
- {/* Main Services */} -
-
-

We Come to You

-

- Living in Japan without strong Japanese skills can make tech problems frustrating. - That's where we come in. Our English-speaking technicians visit your home or office - to help with setup, troubleshooting, and configuration. + {/* Gradient orb accents */} +

+
+ +
+ {/* Eyebrow badge */} +
+ + + English-Speaking Technicians + +
+ + {/* Main heading */} +

+ Tech Help in English, +
+ At Your Door +

+ + {/* Description */} +

+ Need help with your router, computer, or home network? Our technicians visit your home + or office to solve tech problems — explained in a language you understand.

-

- For quick fixes, we also offer remote support. We connect to your device securely over - the internet to diagnose and resolve issues without a home visit. -

-
-
-
- -
-
- {/* Pricing Cards */} -
- {/* Onsite Network & Computer Support */} -
-
- -
-

- Onsite Network & Computer Support -

-
-
Basic Service Fee
-
15,000 JPY
+ {/* Gradient fade to next section */} +
+
+ + {/* How It Works Section */} +
+
+

+ How It Works +

+

+ Getting tech help shouldn't be complicated. Here's our simple process. +

+ +
+ {/* Connecting line (desktop only) */} +
+ + {steps.map((step, index) => { + const Icon = step.icon; + return ( +
+ {/* Step number circle */} +
+ + + {step.number} + +
+ +

{step.title}

+

+ {step.description} +

+
+ ); + })}
- {/* Remote Support */} -
-
- -
-

- Remote Network & Computer Support -

-
-
Basic Service Fee
-
5,000 JPY
+ {/* Gradient fade to pricing section */} +
+
+ + {/* Pricing Cards Section */} +
+ {/* Subtle pattern overlay */} +
+ +
+

+ Support Options +

+

+ Choose the support type that fits your needs. All services include full English + communication. +

+ +
+ {services.map((service, index) => { + const Icon = service.icon; + return ( +
+ {/* Popular badge */} + {service.popular && ( +
+ + Most Popular + +
+ )} + + {/* Icon */} +
+ +
+ + {/* Subtitle */} + + {service.subtitle} + + + {/* Title */} +

{service.title}

+ + {/* Description */} +

+ {service.description} +

+ + {/* Price */} +
+ {service.price} + JPY + + Basic service fee + +
+ + {/* Features */} +
+ {service.features.map(feature => ( +
+ + {feature} +
+ ))} +
+ + {/* CTA */} + + Request This Service + + +
+ ); + })}
- {/* Onsite TV Support */} -
-
- -
-

Onsite TV Support Service

-
-
Basic Service Fee
-
15,000 JPY
-
-
-
+ {/* Gradient fade to FAQ section */} +
+
{/* FAQ Section */} -
-

- Frequently Asked Questions -

-
- - Yes, the Assist Solutions technical team is able to visit your residence for device - set up including Wi-Fi routers, printers, Apple TVs etc. -
-
- Our tech consulting team will be able to make suggestions based on your residence - layout and requirements. Please contact us at info@asolutions.co.jp for a free - consultation. - - } - /> - - - Our In-Home Technical Assistance service can be provided in Tokyo, Saitama and - Kanagawa prefecture. -
-
- *Please note that this service may not available in some areas within the above - prefectures. -
- For more information, please contact us at info@asolutions.co.jp - - } - /> -
-
+
+
+

+ Frequently Asked Questions +

+

+ Common questions about our onsite support services. +

- {/* CTA */} -
-

- Tech Problems? We Speak Your Language. -

-

- Don't struggle with Japanese-only support lines. Get help from technicians who - explain things clearly in English. -

- -
+
+ + Yes, our technical team can visit your residence for device setup including Wi-Fi + routers, mesh systems, printers, Apple TVs, and more. +
+
+ We'll assess your residence layout and recommend the best solution for + complete coverage. Contact us at info@asolutions.co.jp for a free consultation. + + } + /> + + + We typically schedule visits within the same week. For urgent issues, we do our + best to accommodate faster appointments based on availability. +
+
+ Remote support is often available within 1-2 business days if you need faster + help. + + } + /> + +
+
+ + {/* Gradient fade to CTA section */} +
+
+ + {/* CTA Section */} +
+
+
+ + Trusted by expats across Japan +
+

+ Tech Problems? We Speak Your Language. +

+

+ Don't struggle with Japanese-only support lines. Get help from technicians who + explain things clearly in English. +

+ +
+ +
+ + {/* Trust indicators */} +
+
+ + Same-week appointments +
+
+ + English-speaking technicians +
+
+ + 20+ years in Japan +
+
+
+
); } @@ -152,20 +433,30 @@ function FaqItem({ question, answer }: { question: string; answer: React.ReactNo const [isOpen, setIsOpen] = useState(false); return ( -
+
- {isOpen && ( -
{answer}
- )} +
+
+ {answer} +
+
); } diff --git a/apps/portal/src/features/services/components/common/ServicesOverviewContent.tsx b/apps/portal/src/features/services/components/common/ServicesOverviewContent.tsx index 6c5ce408..19e8419f 100644 --- a/apps/portal/src/features/services/components/common/ServicesOverviewContent.tsx +++ b/apps/portal/src/features/services/components/common/ServicesOverviewContent.tsx @@ -1,3 +1,5 @@ +"use client"; + import Link from "next/link"; import { Wifi, @@ -10,10 +12,9 @@ import { Headphones, Building2, Wrench, - Tv, + Zap, + Check, } from "lucide-react"; -import { ServiceCard } from "@/components/molecules/ServiceCard"; -import { ServicesHero } from "@/features/services/components/base/ServicesHero"; interface ServicesOverviewContentProps { /** Base path for service links ("/services" or "/account/services") */ @@ -24,11 +25,74 @@ interface ServicesOverviewContentProps { showCta?: boolean; } +// Service data with enhanced information +const services = [ + { + id: "internet", + icon: Wifi, + title: "Internet", + subtitle: "Fiber Optic", + description: + "NTT Optical Fiber for homes and apartments. Speeds up to 10Gbps with professional installation.", + price: "¥3,200", + priceUnit: "/mo", + features: ["Up to 10Gbps", "NTT Network", "Pro Install"], + useBasePath: true, + }, + { + id: "sim", + icon: Smartphone, + title: "SIM & eSIM", + subtitle: "Mobile Data", + description: + "Data, voice & SMS on NTT Docomo network. Physical SIM or instant eSIM activation.", + price: "¥1,100", + priceUnit: "/mo", + badge: "1st month free", + features: ["Docomo Network", "Voice + Data", "eSIM Ready"], + useBasePath: true, + }, + { + id: "vpn", + icon: ShieldCheck, + title: "VPN Router", + subtitle: "Streaming Access", + description: + "Access US & UK streaming content with a pre-configured router. Simple plug-and-play.", + price: "¥2,500", + priceUnit: "/mo", + features: ["US/UK Content", "Pre-configured", "Plug & Play"], + useBasePath: true, + }, + { + id: "business", + icon: Building2, + title: "Business", + subtitle: "Enterprise IT", + description: + "Enterprise solutions for offices and commercial spaces. Dedicated support and SLAs.", + features: ["Office Setup", "Dedicated Support", "Custom SLAs"], + useBasePath: false, + fixedPath: "/services/business", + }, + { + id: "onsite", + icon: Wrench, + title: "Onsite Support", + subtitle: "Tech Assistance", + description: + "Professional technicians visit your location for setup, troubleshooting, and maintenance.", + features: ["Home Visits", "Setup Help", "Troubleshooting"], + useBasePath: false, + fixedPath: "/services/onsite", + }, +]; + /** - * ServicesOverviewContent - Shared content component for services overview pages. + * ServicesOverviewContent - Enhanced services overview with rich visual design. * - * Used by both PublicServicesOverview and AccountServicesOverview to ensure - * consistent design across public and authenticated service listing pages. + * Features full-width sections, gradient backgrounds, and polished card treatments + * matching the website's landing page aesthetic. */ export function ServicesOverviewContent({ basePath, @@ -36,128 +100,258 @@ export function ServicesOverviewContent({ showCta = true, }: ServicesOverviewContentProps) { return ( -
- {/* Hero */} +
+ {/* Hero Section with gradient background */} {showHero && ( - <> - +
+ {/* Dot grid pattern */} +
+ + {/* Gradient orb accent */} +
+ +
+ {/* Eyebrow badge */} +
+ Full English Support - } - animated - /> +
- {/* Value Props - Compact */} -
-
- - One provider, all services + {/* Main heading */} +

+ Our Services +

+ + {/* Description */} +

+ Connectivity and support solutions designed for Japan's international community. + One provider for all your needs. +

+ + {/* Value propositions */} +
+
+ + + One provider, all services + +
+
+ + English support +
+
+ + Fast activation +
-
- - English support -
-
- - No hidden fees -
-
- +
+ + {/* Gradient fade to services section */} +
+
)} - {/* All Services - Clean Grid with staggered animations */} -
- } - title="Internet" - description="NTT Optical Fiber for homes and apartments. Speeds up to 10Gbps with professional installation." - price="¥3,200/mo" - accentColor="blue" + {/* Services Section */} +
+ {/* Subtle pattern overlay */} +
- } - title="SIM & eSIM" - description="Data, voice & SMS on NTT Docomo network. Physical SIM or instant eSIM activation." - price="¥1,100/mo" - badge="1st month free" - accentColor="green" - /> +
+ {/* Section header */} +
+

Choose Your Service

+
+ + No hidden fees +
+
- } - title="VPN Router" - description="Access US & UK streaming content with a pre-configured router. Simple plug-and-play." - price="¥2,500/mo" - accentColor="purple" - /> + {/* Featured services - Top row (Internet & SIM) */} +
+ {services.slice(0, 2).map((service, index) => { + const Icon = service.icon; + const href = service.useBasePath ? `${basePath}/${service.id}` : service.fixedPath; - {/* Business, Onsite, and TV services only have public pages (no account-specific routes) */} - } - title="Business" - description="Enterprise solutions for offices and commercial spaces. Dedicated support and SLAs." - accentColor="orange" - /> + return ( + + {/* Badge */} + {service.badge && ( +
+ + {service.badge} + +
+ )} - } - title="Onsite Support" - description="Professional technicians visit your location for setup, troubleshooting, and maintenance." - accentColor="cyan" - /> + {/* Icon */} +
+ +
- } - title="TV" - description="Streaming TV packages with international channels. Watch content from home countries." - accentColor="pink" - /> + {/* Subtitle */} + + {service.subtitle} + + + {/* Title */} +

+ {service.title} +

+ + {/* Description */} +

+ {service.description} +

+ + {/* Features */} +
+ {service.features.map(feature => ( + + + {feature} + + ))} +
+ + {/* CTA */} +
+ View Plans + +
+ + ); + })} +
+ + {/* Secondary services - Bottom row */} +
+ {services.slice(2).map((service, index) => { + const Icon = service.icon; + const href = service.useBasePath ? `${basePath}/${service.id}` : service.fixedPath; + + return ( + +
+ {/* Icon */} +
+ +
+ +
+ {/* Subtitle */} + + {service.subtitle} + + + {/* Title */} +

{service.title}

+ + {/* Description */} +

+ {service.description} +

+ + {/* Features as pills */} +
+ {service.features.slice(0, 2).map(feature => ( + + {feature} + + ))} +
+
+
+ + {/* Hover arrow indicator */} +
+ +
+ + ); + })} +
+
+ + {/* Gradient fade to CTA section */} +
- {/* CTA */} + {/* CTA Section */} {showCta && ( -
-

- Need help choosing? -

-

- Our bilingual team can help you find the right solution. -

+
+
+

+ Need help choosing? +

+

+ Our bilingual team is ready to help you find the perfect solution for your needs. Get + personalized recommendations in English. +

-
- - Contact Us - - - - +
+ + Get in Touch + + + + +
)} diff --git a/apps/portal/src/features/subscriptions/components/CancellationFlow/CancellationFlow.tsx b/apps/portal/src/features/subscriptions/components/CancellationFlow/CancellationFlow.tsx index e63dbfd0..6ec7a9e4 100644 --- a/apps/portal/src/features/subscriptions/components/CancellationFlow/CancellationFlow.tsx +++ b/apps/portal/src/features/subscriptions/components/CancellationFlow/CancellationFlow.tsx @@ -408,7 +408,7 @@ export function CancellationFlow({
-
+
diff --git a/apps/portal/src/features/subscriptions/components/sim/ChangePlanModal.tsx b/apps/portal/src/features/subscriptions/components/sim/ChangePlanModal.tsx index d059137c..ee37ed12 100644 --- a/apps/portal/src/features/subscriptions/components/sim/ChangePlanModal.tsx +++ b/apps/portal/src/features/subscriptions/components/sim/ChangePlanModal.tsx @@ -64,7 +64,7 @@ export function ChangePlanModal({ -
+
diff --git a/apps/portal/src/features/subscriptions/components/sim/SimActions.tsx b/apps/portal/src/features/subscriptions/components/sim/SimActions.tsx index 8319b210..60fb8d2b 100644 --- a/apps/portal/src/features/subscriptions/components/sim/SimActions.tsx +++ b/apps/portal/src/features/subscriptions/components/sim/SimActions.tsx @@ -217,7 +217,7 @@ function CancelConfirmModal({
-
+
diff --git a/apps/portal/src/features/subscriptions/components/sim/SimDetailsCard.tsx b/apps/portal/src/features/subscriptions/components/sim/SimDetailsCard.tsx index 80834dd6..031f6701 100644 --- a/apps/portal/src/features/subscriptions/components/sim/SimDetailsCard.tsx +++ b/apps/portal/src/features/subscriptions/components/sim/SimDetailsCard.tsx @@ -319,40 +319,48 @@ export function SimDetailsCard({

-
-
- - - Voicemail {simDetails.voiceMailEnabled ? "Enabled" : "Disabled"} - -
-
- - - Call Waiting {simDetails.callWaitingEnabled ? "Enabled" : "Disabled"} - -
-
+ {simDetails.hasVoice === false ? ( +

+ Data-only plan (no voice features) +

+ ) : ( + <> +
+
+ + + Voicemail {simDetails.voiceMailEnabled ? "Enabled" : "Disabled"} + +
+
+ + + Call Waiting {simDetails.callWaitingEnabled ? "Enabled" : "Disabled"} + +
+
-
- - - Int'l Roaming{" "} - {simDetails.internationalRoamingEnabled ? "Enabled" : "Disabled"} - -
+
+ + + Int'l Roaming{" "} + {simDetails.internationalRoamingEnabled ? "Enabled" : "Disabled"} + +
+ + )}
)} diff --git a/apps/portal/src/features/subscriptions/components/sim/SimManagementSection.tsx b/apps/portal/src/features/subscriptions/components/sim/SimManagementSection.tsx index adcec8df..7b71d701 100644 --- a/apps/portal/src/features/subscriptions/components/sim/SimManagementSection.tsx +++ b/apps/portal/src/features/subscriptions/components/sim/SimManagementSection.tsx @@ -37,6 +37,8 @@ interface SimDetails { callWaitingEnabled?: boolean; internationalRoamingEnabled?: boolean; networkType?: string; + hasVoice?: boolean; + hasSms?: boolean; } interface SimInfo { @@ -304,22 +306,26 @@ export function SimManagementSection({ subscriptionId }: SimManagementSectionPro />
- {/* Voice toggles */} + {/* Voice & Network toggles */}
-

Voice Status

+

+ {simInfo.details.hasVoice === false ? "Network Status" : "Voice Status"} +

{featureError && (
{featureError}
)}
- void updateFeature("voiceMail", checked)} - /> + {simInfo.details.hasVoice !== false && ( + void updateFeature("voiceMail", checked)} + /> + )} void updateFeature("networkType", checked ? "5G" : "4G")} /> - void updateFeature("callWaiting", checked)} - /> - void updateFeature("internationalRoaming", checked)} - /> + {simInfo.details.hasVoice !== false && ( + void updateFeature("callWaiting", checked)} + /> + )} + {simInfo.details.hasVoice !== false && ( + void updateFeature("internationalRoaming", checked)} + /> + )}
+ {simInfo.details.hasVoice === false && ( +

+ Voice features are not available on data-only plans. +

+ )}
diff --git a/apps/portal/src/features/subscriptions/components/sim/TopUpModal.tsx b/apps/portal/src/features/subscriptions/components/sim/TopUpModal.tsx index bdbc7414..12a0df0b 100644 --- a/apps/portal/src/features/subscriptions/components/sim/TopUpModal.tsx +++ b/apps/portal/src/features/subscriptions/components/sim/TopUpModal.tsx @@ -76,7 +76,7 @@ export function TopUpModal({ subscriptionId, onClose, onSuccess, onError }: TopU
-
+
{/* Header */}
diff --git a/apps/portal/src/features/subscriptions/views/SimChangePlan.tsx b/apps/portal/src/features/subscriptions/views/SimChangePlan.tsx index 7a4f84bb..40de66da 100644 --- a/apps/portal/src/features/subscriptions/views/SimChangePlan.tsx +++ b/apps/portal/src/features/subscriptions/views/SimChangePlan.tsx @@ -61,7 +61,15 @@ export function SimChangePlanContainer() { newPlanSku: selectedPlan.sku, newPlanName: selectedPlan.name, }); - setMessage(`Plan change scheduled for ${result.scheduledAt || "the 1st of next month"}`); + const scheduled = result.scheduledAt; + const today = new Date(); + const todayStr = `${today.getFullYear()}${String(today.getMonth() + 1).padStart(2, "0")}${String(today.getDate()).padStart(2, "0")}`; + const isImmediate = scheduled === todayStr; + setMessage( + isImmediate + ? `Plan change submitted for immediate processing (${selectedPlan.name})` + : `Plan change scheduled for ${scheduled || "the 1st of next month"}` + ); setSelectedPlan(null); } catch (e: unknown) { setError( @@ -96,8 +104,9 @@ export function SimChangePlanContainer() {

Change Your Plan

- Select a new plan below. Plan changes will take effect on the 1st of the following - month. Changes must be requested before the 25th of the current month. + {process.env.NODE_ENV === "development" + ? "Select a new plan below. In test mode, plan changes are submitted for immediate processing." + : "Select a new plan below. Plan changes will take effect on the 1st of the following month. Changes must be requested before the 25th of the current month."}

@@ -205,8 +214,17 @@ export function SimChangePlanContainer() {

Important Notes

    -
  • • Plan changes take effect on the 1st of the following month
  • -
  • • Requests must be made before the 25th of the current month
  • + {process.env.NODE_ENV === "development" ? ( + <> +
  • • Test mode: plan changes are submitted for immediate processing
  • +
  • • Changes may take a few hours to appear on the Freebit dashboard
  • + + ) : ( + <> +
  • • Plan changes take effect on the 1st of the following month
  • +
  • • Requests must be made before the 25th of the current month
  • + + )}
  • • Your current data balance will be reset when the new plan activates