From 9d4505d6bebd9ad9ef8fc59fa8e29b714a88a889 Mon Sep 17 00:00:00 2001 From: tema Date: Fri, 5 Sep 2025 18:22:55 +0900 Subject: [PATCH] Refactor SIM management components and enhance user experience - Updated CatalogPage and SimPlansPage to improve descriptions and layout for better clarity. - Removed unused SignalIcon from CatalogPage. - Enhanced ChangePlanModal to allow selection of new plans with improved validation and user feedback. - Updated SimActions to handle navigation and contextual information for actions. - Improved SimDetailsCard to format current plan codes for better readability. - Enhanced SimFeatureToggles to support embedded rendering and streamlined service options management. - Refactored SimManagementSection layout for better organization of SIM details and actions. --- apps/portal/src/app/catalog/page.tsx | 16 +- apps/portal/src/app/catalog/sim/page.tsx | 171 +++++-- .../[id]/sim/change-plan/page.tsx | 101 ++++ .../subscriptions/[id]/sim/reissue/page.tsx | 1 + .../subscriptions/[id]/sim/top-up/page.tsx | 103 ++++ .../components/ChangePlanModal.tsx | 41 +- .../sim-management/components/SimActions.tsx | 140 ++++-- .../components/SimDetailsCard.tsx | 13 +- .../components/SimFeatureToggles.tsx | 39 +- .../components/SimManagementSection.tsx | 91 ++-- .../sim-management/components/TopUpModal.tsx | 2 +- docs/SIM-MANAGEMENT-API-DATA-FLOW.md | 466 ++++++++++++++++++ 12 files changed, 977 insertions(+), 207 deletions(-) create mode 100644 apps/portal/src/app/subscriptions/[id]/sim/change-plan/page.tsx create mode 100644 apps/portal/src/app/subscriptions/[id]/sim/reissue/page.tsx create mode 100644 apps/portal/src/app/subscriptions/[id]/sim/top-up/page.tsx create mode 100644 docs/SIM-MANAGEMENT-API-DATA-FLOW.md diff --git a/apps/portal/src/app/catalog/page.tsx b/apps/portal/src/app/catalog/page.tsx index 76c1a37e..ea3fa62b 100644 --- a/apps/portal/src/app/catalog/page.tsx +++ b/apps/portal/src/app/catalog/page.tsx @@ -9,7 +9,6 @@ import { ArrowRightIcon, WifiIcon, GlobeAltIcon, - SignalIcon, } from "@heroicons/react/24/outline"; import { AnimatedCard } from "@/components/catalog/animated-card"; import { AnimatedButton } from "@/components/catalog/animated-button"; @@ -32,7 +31,7 @@ export default function CatalogPage() {

- Discover high-speed internet, flexible mobile plans, and secure VPN services. Each + Discover high-speed internet, wide range of mobile data options, and secure VPN services. Each solution is personalized based on your location and account eligibility.

@@ -57,13 +56,13 @@ export default function CatalogPage() { {/* SIM/eSIM Service */} } features={[ "Physical SIM & eSIM", - "Data + Voice plans", + "Data + SMS/Voice plans", "Family discounts", - "Flexible data sizes", + "Multiple data options", ]} href="/catalog/sim" color="green" @@ -95,17 +94,12 @@ export default function CatalogPage() {

-
+
} title="Location-Based Plans" description="Internet plans tailored to your house type and available infrastructure" /> - } - title="Smart Recommendations" - description="Personalized plan suggestions based on your account and usage patterns" - /> } title="Seamless Integration" diff --git a/apps/portal/src/app/catalog/sim/page.tsx b/apps/portal/src/app/catalog/sim/page.tsx index e87feab2..419ca360 100644 --- a/apps/portal/src/app/catalog/sim/page.tsx +++ b/apps/portal/src/app/catalog/sim/page.tsx @@ -45,7 +45,7 @@ function PlanTypeSection({ const familyPlans = plans.filter(p => p.hasFamilyDiscount); return ( -
+
{icon}
@@ -224,7 +224,7 @@ export default function SimPlansPage() {

Choose Your SIM Plan

- Flexible mobile data and voice plans with both physical SIM and eSIM options. + Wide range of data options and voice plans with both physical SIM and eSIM options.

{/* Family Discount Banner */} @@ -267,48 +267,54 @@ export default function SimPlansPage() {
{/* Tab Content */} -
- {activeTab === "data-voice" && ( - } - plans={plansByType.DataSmsVoice} - showFamilyDiscount={hasExistingSim} - /> - )} +
+
+ {activeTab === "data-voice" && ( + } + plans={plansByType.DataSmsVoice} + showFamilyDiscount={hasExistingSim} + /> + )} +
- {activeTab === "data-only" && ( - } - plans={plansByType.DataOnly} - showFamilyDiscount={hasExistingSim} - /> - )} +
+ {activeTab === "data-only" && ( + } + plans={plansByType.DataOnly} + showFamilyDiscount={hasExistingSim} + /> + )} +
- {activeTab === "voice-only" && ( - } - plans={plansByType.VoiceOnly} - showFamilyDiscount={hasExistingSim} - /> - )} +
+ {activeTab === "voice-only" && ( + } + plans={plansByType.VoiceOnly} + showFamilyDiscount={hasExistingSim} + /> + )} +
{/* Features Section */}

- All SIM Plans Include + Plan Features & Terms

-
+
-
No Contract
-
Cancel anytime
+
3-Month Contract
+
Minimum 3 billing months
+
+
+
+ +
+
First Month Free
+
Basic fee waived initially
@@ -384,19 +415,53 @@ export default function SimPlansPage() {
Multi-line savings
+
+ +
+
Plan Switching
+
Free data plan changes
+
+
{/* Info Section */} -
- -
-
Getting Started
-

- Choose your plan size, select eSIM or physical SIM, and configure optional add-ons - like voice mail and call waiting. Number porting is available if you want to keep your - existing phone number. -

+
+
+ +
+
Important Terms & Conditions
+
+
+
+
+
+
Contract Period
+

Minimum 3 full billing months required. First month (sign-up to end of month) is free and doesn't count toward contract.

+
+
+
Billing Cycle
+

Monthly billing from 1st to end of month. Regular billing starts on 1st of following month after sign-up.

+
+
+
Cancellation
+

Can be requested online after 3rd month. Service terminates at end of billing cycle.

+
+
+
+
+
Plan Changes
+

Data plan switching is free and takes effect next month. Voice plan changes require new SIM and cancellation policies apply.

+
+
+
Calling/SMS Charges
+

Pay-per-use charges apply separately. Billed 5-6 weeks after usage within billing cycle.

+
+
+
SIM Replacement
+

Reissue fee of 1,500 JPY applies for damaged, lost, or replacement SIM cards.

+
+
diff --git a/apps/portal/src/app/subscriptions/[id]/sim/change-plan/page.tsx b/apps/portal/src/app/subscriptions/[id]/sim/change-plan/page.tsx new file mode 100644 index 00000000..5ccafc26 --- /dev/null +++ b/apps/portal/src/app/subscriptions/[id]/sim/change-plan/page.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { useState, useMemo } from "react"; +import Link from "next/link"; +import { useParams } from "next/navigation"; +import { DashboardLayout } from "@/components/layout/dashboard-layout"; +import { authenticatedApi } from "@/lib/api"; + +const PLAN_CODES = ["PASI_5G", "PASI_10G", "PASI_25G", "PASI_50G"] as const; +type PlanCode = typeof PLAN_CODES[number]; +const PLAN_LABELS: Record = { + PASI_5G: "5GB", + PASI_10G: "10GB", + PASI_25G: "25GB", + PASI_50G: "50GB", +}; + +export default function SimChangePlanPage() { + const params = useParams(); + const subscriptionId = parseInt(params.id as string); + const [currentPlanCode] = useState(""); + const [newPlanCode, setNewPlanCode] = useState<"" | PlanCode>(""); + const [assignGlobalIp, setAssignGlobalIp] = useState(false); + const [scheduledAt, setScheduledAt] = useState(""); + const [message, setMessage] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + const options = useMemo(() => (PLAN_CODES as readonly PlanCode[]).filter(c => c !== (currentPlanCode as PlanCode)), [currentPlanCode]); + + const submit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!newPlanCode) { + setError("Please select a new plan"); + return; + } + setLoading(true); + setMessage(null); + setError(null); + try { + await authenticatedApi.post(`/subscriptions/${subscriptionId}/sim/change-plan`, { + newPlanCode, + assignGlobalIp, + scheduledAt: scheduledAt ? scheduledAt.replace(/-/g, "") : undefined, + }); + setMessage("Plan change submitted successfully"); + } catch (e: any) { + setError(e instanceof Error ? e.message : "Failed to change plan"); + } finally { + setLoading(false); + } + }; + + return ( + +
+
+ ← Back to SIM Management +
+
+

Change Plan

+

Switch to a different data plan. Important: request before the 25th; takes effect on the 1st.

+ + {message &&
{message}
} + {error &&
{error}
} + +
+
+ + +
+ +
+ setAssignGlobalIp(e.target.checked)} className="h-4 w-4 text-blue-600 border-gray-300 rounded" /> + +
+ +
+ + setScheduledAt(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md" /> +
+ +
+ + Back +
+
+
+
+
+ ); +} diff --git a/apps/portal/src/app/subscriptions/[id]/sim/reissue/page.tsx b/apps/portal/src/app/subscriptions/[id]/sim/reissue/page.tsx new file mode 100644 index 00000000..78ad84bf --- /dev/null +++ b/apps/portal/src/app/subscriptions/[id]/sim/reissue/page.tsx @@ -0,0 +1 @@ +export default function Page(){return null} diff --git a/apps/portal/src/app/subscriptions/[id]/sim/top-up/page.tsx b/apps/portal/src/app/subscriptions/[id]/sim/top-up/page.tsx new file mode 100644 index 00000000..351665e0 --- /dev/null +++ b/apps/portal/src/app/subscriptions/[id]/sim/top-up/page.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { useParams } from "next/navigation"; +import { DashboardLayout } from "@/components/layout/dashboard-layout"; +import { authenticatedApi } from "@/lib/api"; + +const PRESETS = [1024, 2048, 5120, 10240, 20480, 51200]; + +export default function SimTopUpPage() { + const params = useParams(); + const subscriptionId = parseInt(params.id as string); + const [amountMb, setAmountMb] = useState(2048); + const [scheduledAt, setScheduledAt] = useState(""); + const [campaignCode, setCampaignCode] = useState(""); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(null); + const [error, setError] = useState(null); + + const format = (mb: number) => (mb % 1024 === 0 ? `${mb / 1024} GB` : `${mb} MB`); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setMessage(null); + setError(null); + try { + await authenticatedApi.post(`/subscriptions/${subscriptionId}/sim/top-up`, { + quotaMb: amountMb, + campaignCode: campaignCode || undefined, + scheduledAt: scheduledAt ? scheduledAt.replace(/-/g, "") : undefined, + }); + setMessage("Top-up submitted successfully"); + } catch (e: any) { + setError(e instanceof Error ? e.message : "Failed to submit top-up"); + } finally { + setLoading(false); + } + }; + + return ( + +
+
+ ← Back to SIM Management +
+
+

Top Up Data

+

Add data quota to your SIM service

+ + {message &&
{message}
} + {error &&
{error}
} + +
+
+ +
+ {PRESETS.map(mb => ( + + ))} +
+
+ +
+ + setCampaignCode(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md" + placeholder="Enter code" + /> +
+ +
+ + setScheduledAt(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md" + /> +

Leave empty to apply immediately

+
+ +
+ + Back +
+
+
+
+
+ ); +} diff --git a/apps/portal/src/features/sim-management/components/ChangePlanModal.tsx b/apps/portal/src/features/sim-management/components/ChangePlanModal.tsx index be43395a..c12cf386 100644 --- a/apps/portal/src/features/sim-management/components/ChangePlanModal.tsx +++ b/apps/portal/src/features/sim-management/components/ChangePlanModal.tsx @@ -6,26 +6,38 @@ import { XMarkIcon } from "@heroicons/react/24/outline"; interface ChangePlanModalProps { subscriptionId: number; + currentPlanCode?: string; onClose: () => void; onSuccess: () => void; onError: (message: string) => void; } -export function ChangePlanModal({ subscriptionId, onClose, onSuccess, onError }: ChangePlanModalProps) { - const [newPlanCode, setNewPlanCode] = useState(""); +export function ChangePlanModal({ subscriptionId, currentPlanCode, onClose, onSuccess, onError }: ChangePlanModalProps) { + const PLAN_CODES = ["PASI_5G", "PASI_10G", "PASI_25G", "PASI_50G"] as const; + type PlanCode = typeof PLAN_CODES[number]; + const PLAN_LABELS: Record = { + PASI_5G: "5GB", + PASI_10G: "10GB", + PASI_25G: "25GB", + PASI_50G: "50GB", + }; + + const allowedPlans = (PLAN_CODES as readonly PlanCode[]).filter(code => code !== (currentPlanCode || '')); + + const [newPlanCode, setNewPlanCode] = useState<"" | PlanCode>(""); const [assignGlobalIp, setAssignGlobalIp] = useState(false); const [scheduledAt, setScheduledAt] = useState(""); // YYYY-MM-DD const [loading, setLoading] = useState(false); const submit = async () => { - if (!newPlanCode.trim()) { - onError("Please enter a new plan code"); + if (!newPlanCode) { + onError("Please select a new plan"); return; } setLoading(true); try { await authenticatedApi.post(`/subscriptions/${subscriptionId}/sim/change-plan`, { - newPlanCode: newPlanCode.trim(), + newPlanCode: newPlanCode, assignGlobalIp, scheduledAt: scheduledAt ? scheduledAt.replaceAll("-", "") : undefined, }); @@ -55,14 +67,18 @@ export function ChangePlanModal({ subscriptionId, onClose, onSuccess, onError }:
- - Select New Plan + +

Only plans different from your current plan are listed.

- Cancel + Back
@@ -113,4 +129,3 @@ export function ChangePlanModal({ subscriptionId, onClose, onSuccess, onError }:
); } - diff --git a/apps/portal/src/features/sim-management/components/SimActions.tsx b/apps/portal/src/features/sim-management/components/SimActions.tsx index 32f36b1f..0f3c2333 100644 --- a/apps/portal/src/features/sim-management/components/SimActions.tsx +++ b/apps/portal/src/features/sim-management/components/SimActions.tsx @@ -1,6 +1,7 @@ "use client"; import React, { useState } from 'react'; +import { useRouter } from 'next/navigation'; import { PlusIcon, ArrowPathIcon, @@ -21,6 +22,7 @@ interface SimActionsProps { onCancelSuccess?: () => void; onReissueSuccess?: () => void; embedded?: boolean; // when true, render content without card container + currentPlanCode?: string; } export function SimActions({ @@ -31,8 +33,10 @@ export function SimActions({ onPlanChangeSuccess, onCancelSuccess, onReissueSuccess, - embedded = false + embedded = false, + currentPlanCode }: SimActionsProps) { + const router = useRouter(); const [showTopUpModal, setShowTopUpModal] = useState(false); const [showCancelConfirm, setShowCancelConfirm] = useState(false); const [showReissueConfirm, setShowReissueConfirm] = useState(false); @@ -40,6 +44,9 @@ export function SimActions({ const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [showChangePlanModal, setShowChangePlanModal] = useState(false); + const [activeInfo, setActiveInfo] = useState< + 'topup' | 'reissue' | 'cancel' | 'changePlan' | null + >(null); const isActive = status === 'active'; const canTopUp = isActive; @@ -145,7 +152,14 @@ export function SimActions({
{/* Top Up Data - Primary Action */}
- {/* Action Descriptions */} -
-
- -
- Top Up Data: Add additional data quota to your SIM service. You can choose the amount and schedule it for later if needed. -
-
- - {simType === 'esim' && ( -
- -
- Reissue eSIM: Generate a new eSIM profile for download. Use this if your previous download failed or you need to install on a new device. + {/* Action Description (contextual) */} + {activeInfo && ( +
+ {activeInfo === 'topup' && ( +
+ +
+ Top Up Data: Add additional data quota to your SIM service. You can choose the amount and schedule it for later if needed. +
-
- )} - -
- -
- Cancel SIM: Permanently cancel your SIM service. This action cannot be undone and will terminate your service immediately. -
+ )} + {activeInfo === 'reissue' && ( +
+ +
+ Reissue eSIM: Generate a new eSIM profile for download. Use this if your previous download failed or you need to install on a new device. +
+
+ )} + {activeInfo === 'cancel' && ( +
+ +
+ Cancel SIM: Permanently cancel your SIM service. This action cannot be undone and will terminate your service immediately. +
+
+ )} + {activeInfo === 'changePlan' && ( +
+ + + +
+ Change Plan: Switch to a different data plan. Important: Plan changes must be requested before the 25th of the month. Changes will take effect on the 1st of the following month. +
+
+ )}
- -
- - - -
- Change Plan: Switch to a different data plan. Important: Plan changes must be requested before the 25th of the month. Changes will take effect on the 1st of the following month. -
-
-
+ )}
{/* Top Up Modal */} {showTopUpModal && ( setShowTopUpModal(false)} + onClose={() => { setShowTopUpModal(false); setActiveInfo(null); }} onSuccess={() => { setShowTopUpModal(false); setSuccess('Data top-up completed successfully'); @@ -270,7 +307,20 @@ export function SimActions({ /> )} - {/* Change Plan handled in Feature Toggles */} + {/* Change Plan Modal */} + {showChangePlanModal && ( + { setShowChangePlanModal(false); setActiveInfo(null); }} + onSuccess={() => { + setShowChangePlanModal(false); + setSuccess('SIM plan change submitted successfully'); + onPlanChangeSuccess?.(); + }} + onError={(message) => setError(message)} + /> + )} {/* Reissue eSIM Confirmation */} {showReissueConfirm && ( @@ -304,11 +354,11 @@ export function SimActions({
@@ -348,11 +398,11 @@ export function SimActions({
diff --git a/apps/portal/src/features/sim-management/components/SimDetailsCard.tsx b/apps/portal/src/features/sim-management/components/SimDetailsCard.tsx index 8ce6fd55..2fefa8b3 100644 --- a/apps/portal/src/features/sim-management/components/SimDetailsCard.tsx +++ b/apps/portal/src/features/sim-management/components/SimDetailsCard.tsx @@ -47,6 +47,15 @@ interface SimDetailsCardProps { } export function SimDetailsCard({ simDetails, isLoading, error, embedded = false, showFeaturesSummary = true }: SimDetailsCardProps) { + const formatPlan = (code?: string) => { + const map: Record = { + PASI_5G: '5GB Plan', + PASI_10G: '10GB Plan', + PASI_25G: '25GB Plan', + PASI_50G: '50GB Plan', + }; + return (code && map[code]) || code || '—'; + }; const getStatusIcon = (status: string) => { switch (status) { case 'active': @@ -146,7 +155,7 @@ export function SimDetailsCard({ simDetails, isLoading, error, embedded = false,

eSIM Details

-

Current Plan: {simDetails.planCode}

+

Current Plan: {formatPlan(simDetails.planCode)}

@@ -229,7 +238,7 @@ export function SimDetailsCard({ simDetails, isLoading, error, embedded = false,

Physical SIM Details

- {simDetails.planCode} • {`${simDetails.size} SIM`} + {formatPlan(simDetails.planCode)} • {`${simDetails.size} SIM`}

diff --git a/apps/portal/src/features/sim-management/components/SimFeatureToggles.tsx b/apps/portal/src/features/sim-management/components/SimFeatureToggles.tsx index 222af827..159bd7e3 100644 --- a/apps/portal/src/features/sim-management/components/SimFeatureToggles.tsx +++ b/apps/portal/src/features/sim-management/components/SimFeatureToggles.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useState } from "react"; import { authenticatedApi } from "@/lib/api"; -import type { SimPlan } from "@/shared/types/catalog.types"; interface SimFeatureTogglesProps { subscriptionId: number; @@ -10,8 +9,8 @@ interface SimFeatureTogglesProps { callWaitingEnabled?: boolean; internationalRoamingEnabled?: boolean; networkType?: string; // '4G' | '5G' - currentPlanCode?: string; onChanged?: () => void; + embedded?: boolean; // when true, render without outer card wrappers } export function SimFeatureToggles({ @@ -20,8 +19,8 @@ export function SimFeatureToggles({ callWaitingEnabled, internationalRoamingEnabled, networkType, - currentPlanCode, onChanged, + embedded = false, }: SimFeatureTogglesProps) { // Initial values const initial = useMemo(() => ({ @@ -29,18 +28,13 @@ export function SimFeatureToggles({ cw: !!callWaitingEnabled, ir: !!internationalRoamingEnabled, nt: networkType === '5G' ? '5G' : '4G', - plan: currentPlanCode || '', - }), [voiceMailEnabled, callWaitingEnabled, internationalRoamingEnabled, networkType, currentPlanCode]); + }), [voiceMailEnabled, callWaitingEnabled, internationalRoamingEnabled, networkType]); // Working values const [vm, setVm] = useState(initial.vm); const [cw, setCw] = useState(initial.cw); const [ir, setIr] = useState(initial.ir); const [nt, setNt] = useState<'4G' | '5G'>(initial.nt as '4G' | '5G'); - const [plan, setPlan] = useState(initial.plan); - - // Plans list - const [plans, setPlans] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); @@ -50,28 +44,13 @@ export function SimFeatureToggles({ setCw(initial.cw); setIr(initial.ir); setNt(initial.nt as '4G' | '5G'); - setPlan(initial.plan); - }, [initial.vm, initial.cw, initial.ir, initial.nt, initial.plan]); - - useEffect(() => { - let ignore = false; - (async () => { - try { - const data = await authenticatedApi.get("/catalog/sim/plans"); - if (!ignore) setPlans(data); - } catch (e) { - // silent; leave plans empty - } - })(); - return () => { ignore = true; }; - }, []); + }, [initial.vm, initial.cw, initial.ir, initial.nt]); const reset = () => { setVm(initial.vm); setCw(initial.cw); setIr(initial.ir); setNt(initial.nt as '4G' | '5G'); - setPlan(initial.plan); setError(null); setSuccess(null); }; @@ -91,10 +70,6 @@ export function SimFeatureToggles({ await authenticatedApi.post(`/subscriptions/${subscriptionId}/sim/features`, featurePayload); } - if (plan && plan !== initial.plan) { - await authenticatedApi.post(`/subscriptions/${subscriptionId}/sim/change-plan`, { newPlanCode: plan }); - } - setSuccess('Changes submitted successfully'); onChanged?.(); } catch (e: any) { @@ -109,9 +84,9 @@ export function SimFeatureToggles({
{/* Service Options */} -
+
-
+
{/* Voice Mail */}
@@ -249,7 +224,7 @@ export function SimFeatureToggles({
{/* Notes and Actions */} -
+
diff --git a/apps/portal/src/features/sim-management/components/SimManagementSection.tsx b/apps/portal/src/features/sim-management/components/SimManagementSection.tsx index 227f025b..dec50c0f 100644 --- a/apps/portal/src/features/sim-management/components/SimManagementSection.tsx +++ b/apps/portal/src/features/sim-management/components/SimManagementSection.tsx @@ -128,49 +128,54 @@ export function SimManagementSection({ subscriptionId }: SimManagementSectionPro {/* SIM Details and Usage - Main Content */}
{/* Main Content Area - Actions and Settings (Left Side) */} -
- {/* SIM Management Actions */} - - - {/* Plan Settings Card */} +
-
-
- - - - -
-
-

Plan Settings

-

Modify service options

-
-
- - +
+

Modify service options

+ +
-
{/* Sidebar - Compact Info (Right Side) */} -
+
+ {/* Details + Usage combined card for mobile-first */} +
+ + +
+ {/* Important Information Card */}
@@ -203,21 +208,7 @@ export function SimManagementSection({ subscriptionId }: SimManagementSectionPro
- - - + {/* (On desktop, details+usage are above; on mobile they appear first since this section is above actions) */}
diff --git a/apps/portal/src/features/sim-management/components/TopUpModal.tsx b/apps/portal/src/features/sim-management/components/TopUpModal.tsx index b84e4457..32084f85 100644 --- a/apps/portal/src/features/sim-management/components/TopUpModal.tsx +++ b/apps/portal/src/features/sim-management/components/TopUpModal.tsx @@ -248,7 +248,7 @@ export function TopUpModal({ subscriptionId, onClose, onSuccess, onError }: TopU disabled={loading} className="w-full sm:w-auto px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50" > - Cancel + Back