Refactor order and subscription services for improved type safety and error handling

- Updated billing cycle assignment in OrderOrchestrator to ensure proper type handling.
- Enhanced error handling in SimManagementService and related components to use more specific types for exceptions.
- Standardized error handling across various components to improve consistency and clarity.
- Adjusted function signatures in multiple services and controllers to return more precise types, enhancing type safety.
This commit is contained in:
tema 2025-09-09 15:59:30 +09:00
parent 05817e8c67
commit de35397cf9
19 changed files with 141 additions and 110 deletions

View File

@ -261,7 +261,10 @@ export class OrderOrchestrator {
quantity: item.Quantity,
unitPrice: item.UnitPrice,
totalPrice: item.TotalPrice,
billingCycle: String(item.PricebookEntry?.Product2?.Billing_Cycle__c || ""),
billingCycle: ((): string | undefined => {
const v = item.PricebookEntry?.Product2?.Billing_Cycle__c;
return typeof v === "string" ? v : undefined;
})(),
});
return acc;
},

View File

@ -1,4 +1,4 @@
import { Injectable, Inject, BadRequestException, NotFoundException } from "@nestjs/common";
import { Injectable, Inject, BadRequestException } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { FreebititService } from "../vendors/freebit/freebit.service";
import { WhmcsService } from "../vendors/whmcs/whmcs.service";
@ -46,7 +46,10 @@ export class SimManagementService {
/**
* Debug method to check subscription data for SIM services
*/
async debugSimSubscription(userId: string, subscriptionId: number): Promise<any> {
async debugSimSubscription(
userId: string,
subscriptionId: number
): Promise<Record<string, unknown>> {
try {
const subscription = await this.subscriptionsService.getSubscriptionById(
userId,
@ -58,11 +61,11 @@ export class SimManagementService {
const expectedEid = "89049032000001000000043598005455";
const simNumberField = Object.entries(subscription.customFields || {}).find(
([key, value]) => value && value.toString().includes(expectedSimNumber)
([_key, value]) => value && value.toString().includes(expectedSimNumber)
);
const eidField = Object.entries(subscription.customFields || {}).find(
([key, value]) => value && value.toString().includes(expectedEid)
([_key, value]) => value && value.toString().includes(expectedEid)
);
return {
@ -199,7 +202,7 @@ export class SimManagementService {
// Check if any field contains the expected SIM number
const expectedSimNumber = "02000331144508";
const foundSimNumber = Object.entries(subscription.customFields || {}).find(
([key, value]) => value && value.toString().includes(expectedSimNumber)
([_key, value]) => value && value.toString().includes(expectedSimNumber)
);
if (foundSimNumber) {

View File

@ -19,13 +19,16 @@ export class SimUsageStoreService {
async upsertToday(account: string, usageMb: number, date?: Date): Promise<void> {
const day = this.normalizeDate(date);
try {
await (this.prisma as any).simUsageDaily.upsert({
where: { account_date: { account, date: day } as any },
await this.prisma.simUsageDaily.upsert({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error composite unique input type depends on Prisma schema
where: { account_date: { account, date: day } as unknown },
update: { usageMb },
create: { account, date: day, usageMb },
});
} catch (e: any) {
this.logger.error("Failed to upsert daily usage", { account, error: e?.message });
} catch (e: unknown) {
const message = e instanceof Error ? e.message : String(e);
this.logger.error("Failed to upsert daily usage", { account, error: message });
}
}
@ -36,7 +39,7 @@ export class SimUsageStoreService {
const end = this.normalizeDate();
const start = new Date(end);
start.setUTCDate(end.getUTCDate() - (days - 1));
const rows = (await (this.prisma as any).simUsageDaily.findMany({
const rows = (await this.prisma.simUsageDaily.findMany({
where: { account, date: { gte: start, lte: end } },
orderBy: { date: "desc" },
})) as Array<{ date: Date; usageMb: number }>;
@ -46,7 +49,7 @@ export class SimUsageStoreService {
async cleanupPreviousMonths(): Promise<number> {
const now = new Date();
const firstOfMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
const result = await (this.prisma as any).simUsageDaily.deleteMany({
const result = await this.prisma.simUsageDaily.deleteMany({
where: { date: { lt: firstOfMonth } },
});
return result.count;

View File

@ -204,7 +204,7 @@ export class SubscriptionsController {
async debugSimSubscription(
@Request() req: RequestWithUser,
@Param("id", ParseIntPipe) subscriptionId: number
) {
): Promise<Record<string, unknown>> {
return this.simManagementService.debugSimSubscription(req.user.id, subscriptionId);
}

View File

@ -6,7 +6,7 @@ import {
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Logger } from "nestjs-pino";
import {
import type {
FreebititConfig,
FreebititAuthRequest,
FreebititAuthResponse,
@ -22,14 +22,13 @@ import {
FreebititPlanChangeResponse,
FreebititCancelPlanRequest,
FreebititCancelPlanResponse,
FreebititEsimReissueRequest,
FreebititEsimReissueResponse,
FreebititEsimAddAccountRequest,
FreebititEsimAddAccountResponse,
FreebititEsimAccountActivationRequest,
FreebititEsimAccountActivationResponse,
SimDetails,
SimUsage,
SimTopUpHistory,
FreebititError,
FreebititAddSpecRequest,
FreebititAddSpecResponse,
} from "./interfaces/freebit.types";
@ -148,9 +147,12 @@ export class FreebititService {
/**
* Make authenticated API request with error handling
*/
private async makeAuthenticatedRequest<T>(endpoint: string, data: any): Promise<T> {
private async makeAuthenticatedRequest<T extends { resultCode: string | number; status?: { message?: string; statusCode?: string | number } }>(
endpoint: string,
data: unknown
): Promise<T> {
const authKey = await this.getAuthKey();
const requestData = { ...data, authKey };
const requestData = { ...(data as Record<string, unknown>), authKey };
try {
const url = `${this.config.baseUrl}${endpoint}`;
@ -164,10 +166,13 @@ export class FreebititService {
if (!response.ok) {
let bodySnippet: string | undefined;
let text: string | null = null;
try {
const text = await response.text();
bodySnippet = text ? text.slice(0, 500) : undefined;
} catch {}
text = await response.text();
} catch (_e) {
text = null;
}
bodySnippet = text ? text.slice(0, 500) : undefined;
this.logger.error("Freebit API non-OK response", {
endpoint,
url,
@ -181,39 +186,39 @@ export class FreebititService {
const responseData = (await response.json()) as T;
// Check for API-level errors
if (responseData && (responseData as any).resultCode !== "100") {
const errorData = responseData as any;
const errorMessage = errorData.status?.message || "Unknown error";
const rc = String(responseData?.resultCode ?? "");
if (rc !== "100") {
const errorMessage = String(responseData.status?.message ?? "Unknown error");
// Provide more specific error messages for common cases
let userFriendlyMessage = `API Error: ${errorMessage}`;
if (errorMessage === "NG") {
userFriendlyMessage = `Account not found or invalid in Freebit system. Please verify the account number exists and is properly configured.`;
} else if (errorMessage.includes("auth") || errorMessage.includes("Auth")) {
} else if (errorMessage.toLowerCase().includes("auth")) {
userFriendlyMessage = `Authentication failed with Freebit API. Please check API credentials.`;
} else if (errorMessage.includes("timeout") || errorMessage.includes("Timeout")) {
} else if (errorMessage.toLowerCase().includes("timeout")) {
userFriendlyMessage = `Request timeout to Freebit API. Please try again later.`;
}
this.logger.error("Freebit API error response", {
endpoint,
resultCode: errorData.resultCode,
statusCode: errorData.status?.statusCode,
resultCode: rc,
statusCode: responseData.status?.statusCode,
message: errorMessage,
userFriendlyMessage,
});
throw new FreebititErrorImpl(
userFriendlyMessage,
errorData.resultCode,
errorData.status?.statusCode,
rc,
String(responseData.status?.statusCode ?? ""),
errorMessage
);
}
this.logger.debug("Freebit API Request Success", {
endpoint,
resultCode: (responseData as any).resultCode,
resultCode: rc,
});
return responseData;
@ -222,12 +227,9 @@ export class FreebititService {
throw error;
}
this.logger.error(`Freebit API request failed: ${endpoint}`, {
error: (error as any).message,
});
throw new InternalServerErrorException(
`Freebit API request failed: ${(error as any).message}`
);
const message = error instanceof Error ? error.message : String(error);
this.logger.error(`Freebit API request failed: ${endpoint}`, { error: message });
throw new InternalServerErrorException(`Freebit API request failed: ${message}`);
}
}
@ -264,7 +266,7 @@ export class FreebititService {
);
let response: FreebititAccountDetailsResponse | undefined;
let lastError: any;
let lastError: unknown;
for (const ep of candidates) {
try {
if (ep !== candidates[0]) {
@ -275,9 +277,9 @@ export class FreebititService {
request
);
break; // success
} catch (err: any) {
} catch (err: unknown) {
lastError = err;
if (typeof err?.message === "string" && err.message.includes("HTTP 404")) {
if (err instanceof Error && err.message.includes("HTTP 404")) {
// try next candidate
continue;
}
@ -293,14 +295,40 @@ export class FreebititService {
);
}
const datas = (response as any).responseDatas;
const list = Array.isArray(datas) ? datas : datas ? [datas] : [];
type AcctDetailItem = {
kind?: string;
account?: string | number;
state?: string;
startDate?: string | number;
relationCode?: string;
resultCode?: string | number;
planCode?: string;
iccid?: string | number;
imsi?: string | number;
eid?: string;
contractLine?: string;
size?: "standard" | "nano" | "micro" | "esim" | string;
sms?: number;
talk?: number;
ipv4?: string;
ipv6?: string;
quota?: number;
async?: { func: string; date: string | number };
voicemail?: number;
voiceMail?: number;
callwaiting?: number;
callWaiting?: number;
worldwing?: number;
worldWing?: number;
};
const datas = response.responseDatas as unknown;
const list = Array.isArray(datas) ? (datas as AcctDetailItem[]) : datas ? [datas as AcctDetailItem] : [];
if (!list.length) {
throw new BadRequestException("No SIM details found for this account");
}
// Prefer the MVNO entry if present
const mvno =
list.find((d: any) => (d.kind || "").toString().toUpperCase() === "MVNO") || list[0];
const mvno = list.find(d => String(d.kind ?? "").toUpperCase() === "MVNO") || list[0];
const simData = mvno;
const startDateRaw = simData.startDate ? String(simData.startDate) : undefined;
@ -348,11 +376,10 @@ export class FreebititService {
});
return simDetails;
} catch (error: any) {
this.logger.error(`Failed to get SIM details for account ${account}`, {
error: error.message,
});
throw error;
} catch (error: unknown) {
const message = error instanceof Error ? error.message : String(error);
this.logger.error(`Failed to get SIM details for account ${account}`, { error: message });
throw error as Error;
}
}
@ -655,7 +682,7 @@ export class FreebititService {
throw new BadRequestException("eSIM EID not found for this account");
}
const payload: import("./interfaces/freebit.types").FreebititEsimAccountActivationRequest = {
const payload: FreebititEsimAccountActivationRequest = {
authKey,
aladinOperated: "20",
createType: "reissue",
@ -686,8 +713,7 @@ export class FreebititService {
throw new InternalServerErrorException(`HTTP ${response.status}: ${response.statusText}`);
}
const data =
(await response.json()) as import("./interfaces/freebit.types").FreebititEsimAccountActivationResponse;
const data = (await response.json()) as FreebititEsimAccountActivationResponse;
const rc =
typeof data.resultCode === "number" ? String(data.resultCode) : data.resultCode || "";
if (rc !== "100") {

View File

@ -18,7 +18,7 @@ export interface FreebititAccountDetailsRequest {
authKey: string;
version?: string | number; // Docs recommend "2"
requestDatas: Array<{
kind: "MASTER" | "MVNO" | string;
kind: string;
account?: string | number;
}>;
}
@ -33,9 +33,9 @@ export interface FreebititAccountDetailsResponse {
// Docs show this can be an array (MASTER + MVNO) or a single object for MVNO
responseDatas:
| {
kind: "MASTER" | "MVNO" | string;
kind: string;
account: string | number;
state: "active" | "suspended" | "temporary" | "waiting" | "obsolete" | string;
state: string;
startDate?: string | number;
relationCode?: string;
resultCode?: string | number;
@ -44,21 +44,21 @@ export interface FreebititAccountDetailsResponse {
imsi?: string | number;
eid?: string;
contractLine?: string;
size?: "standard" | "nano" | "micro" | "esim" | string;
size?: string;
sms?: number; // 10=active, 20=inactive
talk?: number; // 10=active, 20=inactive
ipv4?: string;
ipv6?: string;
quota?: number; // Remaining quota (units vary by env)
async?: {
func: "regist" | "stop" | "resume" | "cancel" | "pinset" | "pinunset" | string;
func: string;
date: string | number;
};
}
| Array<{
kind: "MASTER" | "MVNO" | string;
kind: string;
account: string | number;
state: "active" | "suspended" | "temporary" | "waiting" | "obsolete" | string;
state: string;
startDate?: string | number;
relationCode?: string;
resultCode?: string | number;
@ -67,14 +67,14 @@ export interface FreebititAccountDetailsResponse {
imsi?: string | number;
eid?: string;
contractLine?: string;
size?: "standard" | "nano" | "micro" | "esim" | string;
size?: string;
sms?: number;
talk?: number;
ipv4?: string;
ipv6?: string;
quota?: number;
async?: {
func: "regist" | "stop" | "resume" | "cancel" | "pinset" | "pinunset" | string;
func: string;
date: string | number;
};
}>;
@ -244,13 +244,13 @@ export interface FreebititEsimAccountActivationRequest {
aladinOperated: string; // '10' issue, '20' no-issue
masterAccount?: string;
masterPassword?: string;
createType: "new" | "reissue" | "exchange" | string;
createType: string;
eid?: string; // required for reissue/exchange per business rules
account: string; // MSISDN
simkind: "esim" | string;
simkind: string;
repAccount?: string;
size?: string;
addKind?: "N" | "R" | string; // e.g., 'R' for reissue
addKind?: string; // e.g., 'R' for reissue
oldEid?: string;
oldProductNumber?: string;
mnp?: {
@ -272,7 +272,7 @@ export interface FreebititEsimAccountActivationRequest {
export interface FreebititEsimAccountActivationResponse {
resultCode: number | string;
status?: any;
status?: unknown;
statusCode?: string;
message?: string;
}

View File

@ -5,15 +5,7 @@ import { Invoice, InvoiceList } from "@customer-portal/shared";
import { WhmcsConnectionService } from "./whmcs-connection.service";
import { WhmcsDataTransformer } from "../transformers/whmcs-data.transformer";
import { WhmcsCacheService } from "../cache/whmcs-cache.service";
import {
WhmcsGetInvoicesParams,
WhmcsCreateInvoiceParams,
WhmcsCreateInvoiceResponse,
WhmcsUpdateInvoiceParams,
WhmcsUpdateInvoiceResponse,
WhmcsCapturePaymentParams,
WhmcsCapturePaymentResponse,
} from "../types/whmcs-api.types";
import { WhmcsGetInvoicesParams } from "../types/whmcs-api.types";
export interface InvoiceFilters {
status?: "Paid" | "Unpaid" | "Cancelled" | "Overdue" | "Collections";

View File

@ -1,4 +1,6 @@
#!/usr/bin/env node
/* eslint-env node */
/* eslint-disable no-console */
// Ensure dev-time Next.js manifests exist to avoid noisy ENOENT errors
import { mkdirSync, existsSync, writeFileSync } from "fs";
import { join } from "path";

View File

@ -457,7 +457,7 @@ export default function SimPlansPage() {
<div className="font-medium text-blue-900">Contract Period</div>
<p className="text-blue-800">
Minimum 3 full billing months required. First month (sign-up to end of month) is
free and doesn't count toward contract.
free and doesn&apos;t count toward contract.
</p>
</div>
<div>

View File

@ -12,8 +12,6 @@ import {
CubeIcon,
StarIcon,
WrenchScrewdriverIcon,
PlusIcon,
BoltIcon,
ExclamationTriangleIcon,
EnvelopeIcon,
PhoneIcon,

View File

@ -20,7 +20,7 @@ export default function SimCancelPage() {
try {
await authenticatedApi.post(`/subscriptions/${subscriptionId}/sim/cancel`, {});
setMessage("SIM service cancelled successfully");
} catch (e: any) {
} catch (e: unknown) {
setError(e instanceof Error ? e.message : "Failed to cancel SIM service");
} finally {
setLoading(false);
@ -62,7 +62,7 @@ export default function SimCancelPage() {
<div className="flex gap-3">
<button
onClick={submit}
onClick={() => void submit()}
disabled={loading}
className="px-4 py-2 rounded-md bg-red-600 text-white text-sm disabled:opacity-50"
>

View File

@ -43,7 +43,7 @@ export default function SimChangePlanPage() {
newPlanCode,
});
setMessage("Plan change submitted successfully");
} catch (e: any) {
} catch (e: unknown) {
setError(e instanceof Error ? e.message : "Failed to change plan");
} finally {
setLoading(false);
@ -79,7 +79,7 @@ export default function SimChangePlanPage() {
</div>
)}
<form onSubmit={submit} className="space-y-6">
<form onSubmit={(e) => void submit(e)} className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">New Plan</label>
<select

View File

@ -46,7 +46,7 @@ export default function SimTopUpPage() {
quotaMb: getCurrentAmountMb(),
});
setMessage(`Successfully topped up ${gbAmount} GB for ¥${calculateCost().toLocaleString()}`);
} catch (e: any) {
} catch (e: unknown) {
setError(e instanceof Error ? e.message : "Failed to submit top-up");
} finally {
setLoading(false);
@ -83,7 +83,7 @@ export default function SimTopUpPage() {
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6">
<form onSubmit={(e) => void handleSubmit(e)} className="space-y-6">
{/* Amount Input */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Amount (GB)</label>

View File

@ -46,7 +46,7 @@ export function ChangePlanModal({
newPlanCode: newPlanCode,
});
onSuccess();
} catch (e: any) {
} catch (e: unknown) {
onError(e instanceof Error ? e.message : "Failed to change plan");
} finally {
setLoading(false);
@ -103,7 +103,7 @@ export function ChangePlanModal({
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
onClick={submit}
onClick={() => void submit()}
disabled={loading}
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"
>

View File

@ -242,8 +242,8 @@ export function DataUsageChart({
<div>
<h4 className="text-sm font-medium text-red-800">High Usage Warning</h4>
<p className="text-sm text-red-700 mt-1">
You've used {usagePercentage.toFixed(1)}% of your data quota. Consider topping up
to avoid service interruption.
You have used {usagePercentage.toFixed(1)}% of your data quota. Consider topping
up to avoid service interruption.
</p>
</div>
</div>
@ -257,8 +257,8 @@ export function DataUsageChart({
<div>
<h4 className="text-sm font-medium text-yellow-800">Usage Notice</h4>
<p className="text-sm text-yellow-700 mt-1">
You've used {usagePercentage.toFixed(1)}% of your data quota. Consider monitoring
your usage.
You have used {usagePercentage.toFixed(1)}% of your data quota. Consider
monitoring your usage.
</p>
</div>
</div>

View File

@ -63,7 +63,7 @@ export function SimActions({
setSuccess("eSIM profile reissued successfully");
setShowReissueConfirm(false);
onReissueSuccess?.();
} catch (error: any) {
} catch (error: unknown) {
setError(error instanceof Error ? error.message : "Failed to reissue eSIM profile");
} finally {
setLoading(null);
@ -80,7 +80,7 @@ export function SimActions({
setSuccess("SIM service cancelled successfully");
setShowCancelConfirm(false);
onCancelSuccess?.();
} catch (error: any) {
} catch (error: unknown) {
setError(error instanceof Error ? error.message : "Failed to cancel SIM service");
} finally {
setLoading(null);
@ -399,7 +399,7 @@ export function SimActions({
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
onClick={handleReissueEsim}
onClick={() => void handleReissueEsim()}
disabled={loading === "reissue"}
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-600 text-base font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"
>
@ -449,7 +449,7 @@ export function SimActions({
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
onClick={handleCancelSim}
onClick={() => void handleCancelSim()}
disabled={loading === "cancel"}
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"
>

View File

@ -63,22 +63,24 @@ export function SimFeatureToggles({
setError(null);
setSuccess(null);
try {
const featurePayload: any = {};
const featurePayload: {
voiceMailEnabled?: boolean;
callWaitingEnabled?: boolean;
internationalRoamingEnabled?: boolean;
networkType?: "4G" | "5G";
} = {};
if (vm !== initial.vm) featurePayload.voiceMailEnabled = vm;
if (cw !== initial.cw) featurePayload.callWaitingEnabled = cw;
if (ir !== initial.ir) featurePayload.internationalRoamingEnabled = ir;
if (nt !== initial.nt) featurePayload.networkType = nt;
if (Object.keys(featurePayload).length > 0) {
await authenticatedApi.post(
`/subscriptions/${subscriptionId}/sim/features`,
featurePayload
);
await authenticatedApi.post(`/subscriptions/${subscriptionId}/sim/features`, featurePayload);
}
setSuccess("Changes submitted successfully");
onChanged?.();
} catch (e: any) {
} catch (e: unknown) {
setError(e instanceof Error ? e.message : "Failed to submit changes");
} finally {
setLoading(false);
@ -347,7 +349,7 @@ export function SimFeatureToggles({
<div className="flex flex-col sm:flex-row gap-3">
<button
onClick={applyChanges}
onClick={() => void applyChanges()}
disabled={loading}
className="flex-1 inline-flex items-center justify-center px-6 py-3 border border-transparent rounded-lg text-sm font-semibold text-white bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200"
>
@ -389,7 +391,7 @@ export function SimFeatureToggles({
)}
</button>
<button
onClick={reset}
onClick={() => reset()}
disabled={loading}
className="inline-flex items-center justify-center px-6 py-3 border border-gray-300 rounded-lg text-sm font-semibold 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 disabled:cursor-not-allowed transition-colors duration-200"
>

View File

@ -36,12 +36,14 @@ export function SimManagementSection({ subscriptionId }: SimManagementSectionPro
}>(`/subscriptions/${subscriptionId}/sim`);
setSimInfo(data);
} catch (error: any) {
if (error.status === 400) {
} catch (err: unknown) {
const hasStatus = (v: unknown): v is { status: number } =>
typeof v === 'object' && v !== null && 'status' in v && typeof (v as { status: unknown }).status === 'number';
if (hasStatus(err) && err.status === 400) {
// Not a SIM subscription - this component shouldn't be shown
setError("This subscription is not a SIM service");
} else {
setError(error instanceof Error ? error.message : "Failed to load SIM information");
setError(err instanceof Error ? err.message : "Failed to load SIM information");
}
} finally {
setLoading(false);
@ -49,17 +51,17 @@ export function SimManagementSection({ subscriptionId }: SimManagementSectionPro
};
useEffect(() => {
fetchSimInfo();
void fetchSimInfo();
}, [subscriptionId]);
const handleRefresh = () => {
setLoading(true);
fetchSimInfo();
void fetchSimInfo();
};
const handleActionSuccess = () => {
// Refresh SIM info after any successful action
fetchSimInfo();
void fetchSimInfo();
};
if (loading) {

View File

@ -48,7 +48,7 @@ export function TopUpModal({ subscriptionId, onClose, onSuccess, onError }: TopU
await authenticatedApi.post(`/subscriptions/${subscriptionId}/sim/top-up`, requestBody);
onSuccess();
} catch (error: any) {
} catch (error: unknown) {
onError(error instanceof Error ? error.message : "Failed to top up SIM");
} finally {
setLoading(false);
@ -87,7 +87,7 @@ export function TopUpModal({ subscriptionId, onClose, onSuccess, onError }: TopU
</button>
</div>
<form onSubmit={handleSubmit}>
<form onSubmit={(e) => void handleSubmit(e)}>
{/* Amount Input */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">Amount (GB)</label>