\n Minimum 3 full billing months required. First month (sign-up to end of month) is\n free and doesn't count toward contract.\n
\n
\n
\n
Billing Cycle
\n
\n Monthly billing from 1st to end of month. Regular billing starts on 1st of\n following month after sign-up.\n
\n
\n
\n
Cancellation
\n
\n Can be requested online after 3rd month. Service terminates at end of billing\n cycle.\n
\n
\n
\n
\n
\n
Plan Changes
\n
\n Data plan switching is free and takes effect next month. Voice plan changes\n require new SIM and cancellation policies apply.\n
\n
\n
\n
Calling/SMS Charges
\n
\n Pay-per-use charges apply separately. Billed 5-6 weeks after usage within billing\n cycle.\n
\n
\n
\n
SIM Replacement
\n
\n Reissue fee of 1,500 JPY applies for damaged, lost, or replacement SIM cards.\n
\n
\n
\n
\n
\n
\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/app/(portal)/catalog/vpn/page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/app/(portal)/checkout/page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/app/(portal)/dashboard/page.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'useDashboardStore' is defined but never used.","line":9,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":9,"endColumn":27},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'CreditCardIcon' is defined but never used.","line":14,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":14,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ExclamationTriangleIcon' is defined but never used.","line":17,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":17,"endColumn":26},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'PlusIcon' is defined but never used.","line":19,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":19,"endColumn":11},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ArrowTrendingUpIcon' is defined but never used.","line":21,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":21,"endColumn":22},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'BellIcon' is defined but never used.","line":23,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":23,"endColumn":11},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ClipboardDocumentListIcon' is defined but never used.","line":24,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":24,"endColumn":28},{"ruleId":"prettier/prettier","severity":1,"message":"Delete `;`","line":56,"column":9,"nodeType":null,"messageId":"delete","endLine":56,"endColumn":10,"fix":{"range":[2157,2158],"text":""}},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'truncateName' is defined but never used.","line":298,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":298,"endColumn":22},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":303,"column":85,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":303,"endColumn":88,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[12711,12714],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[12711,12714],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-argument","severity":2,"message":"Unsafe argument of type `any` assigned to a parameter of type `{ nextInvoice?: { id: number; } | null | undefined; stats?: { unpaidInvoices?: number | undefined; openCases?: number | undefined; } | undefined; }`.","line":307,"column":40,"nodeType":"Identifier","messageId":"unsafeArgument","endLine":307,"endColumn":47}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":9,"fixableErrorCount":0,"fixableWarningCount":1,"source":"\"use client\";\nimport { logger } from \"@/lib/logger\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport { useRouter } from \"next/navigation\";\nimport { useAuthStore } from \"@/lib/auth/store\";\nimport { useDashboardSummary } from \"@/features/dashboard/hooks/useDashboardSummary\";\nimport { useDashboardStore } from \"@/features/dashboard/stores/dashboard.store\";\nimport { generateDashboardTasks } from \"@/features/dashboard/utils/dashboard.utils\";\n\nimport type { Activity } from \"@customer-portal/shared\";\nimport {\n CreditCardIcon,\n ServerIcon,\n ChatBubbleLeftRightIcon,\n ExclamationTriangleIcon,\n ChevronRightIcon,\n PlusIcon,\n DocumentTextIcon,\n ArrowTrendingUpIcon,\n CalendarDaysIcon,\n BellIcon,\n ClipboardDocumentListIcon,\n} from \"@heroicons/react/24/outline\";\nimport {\n CreditCardIcon as CreditCardIconSolid,\n ServerIcon as ServerIconSolid,\n ChatBubbleLeftRightIcon as ChatBubbleLeftRightIconSolid,\n ClipboardDocumentListIcon as ClipboardDocumentListIconSolid,\n} from \"@heroicons/react/24/solid\";\nimport { format, formatDistanceToNow } from \"date-fns\";\nimport { StatCard, QuickAction, ActivityFeed } from \"@/features/dashboard/components\";\nimport { LoadingSpinner } from \"@/components/ui/loading-spinner\";\nimport { ErrorState } from \"@/components/ui/error-state\";\nimport { formatCurrency, getCurrencyLocale } from \"@/utils/currency\";\n\nexport default function DashboardPage() {\n const router = useRouter();\n const { user, isAuthenticated, isLoading: authLoading } = useAuthStore();\n const { data: summary, isLoading: summaryLoading, error } = useDashboardSummary();\n\n const [paymentLoading, setPaymentLoading] = useState(false);\n const [paymentError, setPaymentError] = useState(null);\n\n // Handle Pay Now functionality\n const handlePayNow = (invoiceId: number) => {\n setPaymentLoading(true);\n setPaymentError(null);\n\n void (async () => {\n try {\n const { BillingService } = await import(\"@/features/billing/services/billing.service\");\n const ssoLink = await BillingService.createInvoiceSsoLink({ invoiceId, target: \"pay\" });\n // Centralized SSO link opening\n ;(await import(\"@/lib/utils/sso\")).openSsoLink(ssoLink.url, { newTab: true });\n } catch (error) {\n logger.error(error, \"Failed to create payment link\");\n setPaymentError(error instanceof Error ? error.message : \"Failed to open payment page\");\n } finally {\n setPaymentLoading(false);\n }\n })();\n };\n\n // Handle activity item clicks\n const handleActivityClick = (activity: Activity) => {\n if (activity.type === \"invoice_created\" || activity.type === \"invoice_paid\") {\n // Use the related invoice ID for navigation\n if (activity.relatedId) {\n router.push(`/billing/invoices/${activity.relatedId}`);\n }\n }\n };\n\n if (authLoading || summaryLoading || !isAuthenticated) {\n return (\n
\n >\n );\n}\n\n// Helpers and small components (local to dashboard)\nfunction truncateName(name: string, len = 28) {\n if (name.length <= len) return name;\n return name.slice(0, Math.max(0, len - 1)) + \"…\";\n}\n\nfunction TasksChip({ summaryLoading, summary }: { summaryLoading: boolean; summary: any }) {\n const router = useRouter();\n if (summaryLoading) return null;\n\n const tasks = generateDashboardTasks(summary);\n const count = tasks.length;\n if (count === 0) return null;\n\n return (\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/app/(portal)/layout.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/app/(portal)/orders/[id]/page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/app/(portal)/orders/page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/app/(portal)/subscriptions/[id]/page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/app/(portal)/subscriptions/[id]/sim/cancel/page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/app/(portal)/subscriptions/[id]/sim/change-plan/page.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":49,"column":17,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":49,"endColumn":20,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1667,1670],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1667,1670],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":83,"column":24,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":83,"endColumn":32}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useState, useMemo } from \"react\";\nimport Link from \"next/link\";\nimport { useParams } from \"next/navigation\";\nimport { simActionsService } from \"@/features/subscriptions/services/sim-actions.service\";\n\nconst PLAN_CODES = [\"PASI_5G\", \"PASI_10G\", \"PASI_25G\", \"PASI_50G\"] as const;\ntype PlanCode = (typeof PLAN_CODES)[number];\nconst PLAN_LABELS: Record = {\n PASI_5G: \"5GB\",\n PASI_10G: \"10GB\",\n PASI_25G: \"25GB\",\n PASI_50G: \"50GB\",\n};\n\nexport default function SimChangePlanPage() {\n const params = useParams();\n const subscriptionId = parseInt(params.id as string);\n const [currentPlanCode] = useState(\"\");\n const [newPlanCode, setNewPlanCode] = useState<\"\" | PlanCode>(\"\");\n const [assignGlobalIp, setAssignGlobalIp] = useState(false);\n const [scheduledAt, setScheduledAt] = useState(\"\");\n const [message, setMessage] = useState(null);\n const [error, setError] = useState(null);\n const [loading, setLoading] = useState(false);\n\n const options = useMemo(\n () => (PLAN_CODES as readonly PlanCode[]).filter(c => c !== (currentPlanCode as PlanCode)),\n [currentPlanCode]\n );\n\n const submit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!newPlanCode) {\n setError(\"Please select a new plan\");\n return;\n }\n setLoading(true);\n setMessage(null);\n setError(null);\n try {\n await simActionsService.changePlan(subscriptionId, {\n newPlanCode,\n assignGlobalIp,\n scheduledAt: scheduledAt ? scheduledAt.replace(/-/g, \"\") : undefined,\n });\n setMessage(\"Plan change submitted successfully\");\n } catch (e: any) {\n setError(e instanceof Error ? e.message : \"Failed to change plan\");\n } finally {\n setLoading(false);\n }\n };\n\n return (\n
\n
\n \n ← Back to SIM Management\n \n
\n
\n
Change Plan
\n
\n Change Plan: Switch to a different data plan. Important: Plan changes must be requested\n before the 25th of the month. Changes will take effect on the 1st of the following month.\n
\n );\n}\n\nexport function PageLoadingState({ title }: { title: string }) {\n return (\n
\n
\n {/* Header skeleton */}\n
\n
\n \n
\n \n \n
\n
\n
\n\n {/* Content skeleton */}\n
\n \n \n
\n
\n
\n );\n}\n\nexport function FullPageLoadingState({ title }: { title: string }) {\n return (\n
\n
\n \n
\n
{title}
\n
Please wait while we load your content...
\n
\n
\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/components/ui/loading-spinner.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/components/ui/logo.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/components/ui/progress-steps.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/components/ui/status-pill.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/components/ui/step-header.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/components/ui/sub-card.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/account/components/AddressCard.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/account/components/PasswordChangeCard.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/account/components/PersonalInfoCard.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/account/containers/Profile.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'loading' is assigned a value but never used.","line":14,"column":5,"nodeType":null,"messageId":"unusedVar","endLine":14,"endColumn":12},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'billingInfo' is assigned a value but never used.","line":16,"column":5,"nodeType":null,"messageId":"unusedVar","endLine":16,"endColumn":16},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":76,"column":18,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":79,"endColumn":13},{"ruleId":"@typescript-eslint/no-unsafe-assignment","severity":2,"message":"Unsafe assignment of an `any` value.","line":83,"column":20,"nodeType":"TSAsExpression","messageId":"anyAssignment","endLine":83,"endColumn":38},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":83,"column":35,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":83,"endColumn":38,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2757,2760],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2757,2760],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":89,"column":18,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":92,"endColumn":13},{"ruleId":"@typescript-eslint/no-unsafe-argument","severity":2,"message":"Unsafe argument of type `any` assigned to a parameter of type `AddressData`.","line":90,"column":42,"nodeType":"TSAsExpression","messageId":"unsafeArgument","endLine":90,"endColumn":60},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":90,"column":57,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":90,"endColumn":60,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3055,3058],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3055,3058],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-argument","severity":2,"message":"Unsafe argument of type `any` assigned to a parameter of type `SetStateAction`.","line":93,"column":51,"nodeType":"TSAsExpression","messageId":"unsafeArgument","endLine":93,"endColumn":62},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":93,"column":59,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":93,"endColumn":62,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3180,3183],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3180,3183],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":8,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useState } from \"react\";\nimport { PageLayout } from \"@/components/layout/PageLayout\";\nimport { useAuthStore } from \"@/lib/auth/store\";\nimport { useProfileData } from \"../hooks/useProfileData\";\nimport { PersonalInfoCard } from \"../components/PersonalInfoCard\";\nimport { AddressCard } from \"../components/AddressCard\";\nimport { PasswordChangeCard } from \"../components/PasswordChangeCard\";\n\nexport function ProfileContainer() {\n const { user } = useAuthStore();\n const {\n loading,\n error,\n billingInfo,\n formData,\n setFormData,\n addressData,\n setAddressData,\n saveProfile,\n saveAddress,\n isSavingProfile,\n isSavingAddress,\n } = useProfileData();\n\n const [isEditingInfo, setIsEditingInfo] = useState(false);\n const [isEditingAddress, setIsEditingAddress] = useState(false);\n\n const [pwdError, setPwdError] = useState(null);\n const [pwdSuccess, setPwdSuccess] = useState(null);\n const [isChangingPassword, setIsChangingPassword] = useState(false);\n const [pwdForm, setPwdForm] = useState({\n currentPassword: \"\",\n newPassword: \"\",\n confirmPassword: \"\",\n });\n\n const handleChangePassword = async () => {\n setIsChangingPassword(true);\n setPwdError(null);\n setPwdSuccess(null);\n try {\n if (!pwdForm.currentPassword || !pwdForm.newPassword) {\n setPwdError(\"Please fill in all password fields\");\n return;\n }\n if (pwdForm.newPassword !== pwdForm.confirmPassword) {\n setPwdError(\"New password and confirmation do not match\");\n return;\n }\n await useAuthStore.getState().changePassword(pwdForm.currentPassword, pwdForm.newPassword);\n setPwdSuccess(\"Password changed successfully.\");\n setPwdForm({ currentPassword: \"\", newPassword: \"\", confirmPassword: \"\" });\n } catch (err) {\n setPwdError(err instanceof Error ? err.message : \"Failed to change password\");\n } finally {\n setIsChangingPassword(false);\n }\n };\n\n return (\n >}\n >\n
\n setIsEditingInfo(true)}\n onCancel={() => setIsEditingInfo(false)}\n onChange={(field, value) => setFormData(prev => ({ ...prev, [field]: value }))}\n onSave={async () => {\n const ok = await saveProfile(formData);\n if (ok) setIsEditingInfo(false);\n }}\n />\n\n setIsEditingAddress(true)}\n onCancel={() => setIsEditingAddress(false)}\n onSave={async () => {\n const ok = await saveAddress(addressData as any);\n if (ok) setIsEditingAddress(false);\n }}\n onAddressChange={addr => setAddressData(addr as any)}\n />\n\n setPwdForm(prev => ({ ...prev, ...next }))}\n onSubmit={() => {\n void handleChangePassword();\n }}\n />\n
\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/account/containers/ProfileContainer.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'PageLayout' is defined but never used.","line":4,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":4,"endColumn":20},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has a missing dependency: 'address'. Either include it or remove the dependency array.","line":63,"column":6,"nodeType":"ArrayExpression","endLine":63,"endColumn":8,"suggestions":[{"desc":"Update the dependencies array to be: [address]","fix":{"range":[1932,1934],"text":"[address]"}}]},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":187,"column":27,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":190,"endColumn":21},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":265,"column":29,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":268,"endColumn":23}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { PageLayout } from \"@/components/layout/PageLayout\";\nimport { LoadingSpinner } from \"@/components/ui/loading-spinner\";\nimport {\n ExclamationTriangleIcon,\n MapPinIcon,\n PencilIcon,\n CheckIcon,\n XMarkIcon,\n UserIcon,\n} from \"@heroicons/react/24/outline\";\nimport { useAuthStore } from \"@/lib/auth/store\";\nimport { accountService } from \"@/features/account/services/account.service\";\nimport { useProfileEdit } from \"@/features/account/hooks/useProfileEdit\";\nimport { AddressForm } from \"@/features/catalog/components/base/AddressForm\";\nimport { useAddressEdit } from \"@/features/account/hooks/useAddressEdit\";\n\nexport default function ProfileContainer() {\n const { user } = useAuthStore();\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState(null);\n const [editingProfile, setEditingProfile] = useState(false);\n const [editingAddress, setEditingAddress] = useState(false);\n\n const profile = useProfileEdit({\n firstName: user?.firstName || \"\",\n lastName: user?.lastName || \"\",\n phone: user?.phone || \"\",\n });\n\n const address = useAddressEdit({\n street: \"\",\n streetLine2: \"\",\n city: \"\",\n state: \"\",\n postalCode: \"\",\n country: \"\",\n });\n\n useEffect(() => {\n void (async () => {\n try {\n setLoading(true);\n const addr = await accountService.getAddress().catch(() => null);\n if (addr) {\n address.setForm({\n street: addr.street ?? \"\",\n streetLine2: addr.streetLine2 ?? \"\",\n city: addr.city ?? \"\",\n state: addr.state ?? \"\",\n postalCode: addr.postalCode ?? \"\",\n country: addr.country ?? \"\",\n });\n }\n } catch (e) {\n setError(e instanceof Error ? e.message : \"Failed to load profile data\");\n } finally {\n setLoading(false);\n }\n })();\n }, []);\n\n if (loading) {\n return (\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/account/hooks/useAddressEdit.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/account/hooks/useAddressForm.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/account/hooks/useProfileData.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/account/hooks/useProfileEdit.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/account/services/account.service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/LoginForm/LoginForm.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":67,"column":77,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":67,"endColumn":80,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1678,1681],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1678,1681],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-argument","severity":2,"message":"Unsafe argument of type `any` assigned to a parameter of type `string`.","line":71,"column":34,"nodeType":"Identifier","messageId":"unsafeArgument","endLine":71,"endColumn":39},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useCallback has a missing dependency: 'validationConfig'. Either include it or remove the dependency array.","line":73,"column":6,"nodeType":"ArrayExpression","endLine":73,"endColumn":8,"suggestions":[{"desc":"Update the dependencies array to be: [validationConfig]","fix":{"range":[1900,1902],"text":"[validationConfig]"}}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":77,"column":41,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":77,"endColumn":44,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2012,2015],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2012,2015],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-assignment","severity":2,"message":"Unsafe assignment of an `any` value.","line":78,"column":39,"nodeType":"Property","messageId":"anyAssignment","endLine":78,"endColumn":53},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":165,"column":20,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":165,"endColumn":34},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `'`, `‘`, `'`, `’`.","line":240,"column":46,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[6828,6851],"text":"Don't have an account? "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"‘"},"fix":{"range":[6828,6851],"text":"Don‘t have an account? "},"desc":"Replace with `‘`."},{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[6828,6851],"text":"Don't have an account? "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"’"},"fix":{"range":[6828,6851],"text":"Don’t have an account? "},"desc":"Replace with `’`."}]}],"suppressedMessages":[],"errorCount":6,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Login Form Component\n * Reusable login form with validation and error handling\n */\n\n\"use client\";\n\nimport { useState, useCallback } from \"react\";\nimport Link from \"next/link\";\nimport { Button, Input, ErrorMessage } from \"@/components/ui\";\nimport { FormField } from \"@/components/common/FormField\";\nimport { useLogin } from \"../../hooks/use-auth\";\nimport { validationRules, validateField } from \"@/lib/form-validation\";\n\ninterface LoginFormProps {\n onSuccess?: () => void;\n onError?: (error: string) => void;\n showSignupLink?: boolean;\n showForgotPasswordLink?: boolean;\n className?: string;\n}\n\ninterface LoginFormData {\n email: string;\n password: string;\n rememberMe: boolean;\n}\n\ninterface FormErrors {\n email?: string;\n password?: string;\n general?: string;\n}\n\nexport function LoginForm({\n onSuccess,\n onError,\n showSignupLink = true,\n showForgotPasswordLink = true,\n className = \"\",\n}: LoginFormProps) {\n const { login, loading, error, clearError } = useLogin();\n\n const [formData, setFormData] = useState({\n email: \"\",\n password: \"\",\n rememberMe: false,\n });\n\n const [errors, setErrors] = useState({});\n const [touched, setTouched] = useState>({\n email: false,\n password: false,\n rememberMe: false,\n });\n\n // Validation rules\n const validationConfig = {\n email: [\n validationRules.required(\"Email is required\"),\n validationRules.email(\"Please enter a valid email address\"),\n ],\n password: [validationRules.required(\"Password is required\")],\n };\n\n // Validate field\n const validateFormField = useCallback((field: keyof LoginFormData, value: any) => {\n const rules = validationConfig[field as keyof typeof validationConfig];\n if (!rules) return null;\n\n const result = validateField(value, rules);\n return result.isValid ? null : result.errors[0];\n }, []);\n\n // Handle field change\n const handleFieldChange = useCallback(\n (field: keyof LoginFormData, value: any) => {\n setFormData(prev => ({ ...prev, [field]: value }));\n\n // Clear general error when user starts typing\n if (errors.general) {\n setErrors(prev => ({ ...prev, general: undefined }));\n clearError();\n }\n\n // Validate field if it has been touched\n if (touched[field]) {\n const fieldError = validateFormField(field, value);\n setErrors(prev => ({ ...prev, [field]: fieldError || undefined }));\n }\n },\n [errors.general, touched, validateFormField, clearError]\n );\n\n // Handle field blur\n const handleFieldBlur = useCallback(\n (field: keyof LoginFormData) => {\n setTouched(prev => ({ ...prev, [field]: true }));\n\n const fieldError = validateFormField(field, formData[field]);\n setErrors(prev => ({ ...prev, [field]: fieldError || undefined }));\n },\n [formData, validateFormField]\n );\n\n // Validate entire form\n const validateForm = useCallback(() => {\n const newErrors: FormErrors = {};\n let isValid = true;\n\n // Validate email\n const emailError = validateFormField(\"email\", formData.email);\n if (emailError) {\n newErrors.email = emailError;\n isValid = false;\n }\n\n // Validate password\n const passwordError = validateFormField(\"password\", formData.password);\n if (passwordError) {\n newErrors.password = passwordError;\n isValid = false;\n }\n\n setErrors(newErrors);\n return isValid;\n }, [formData, validateFormField]);\n\n // Handle form submission\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n\n // Mark all fields as touched\n setTouched({\n email: true,\n password: true,\n rememberMe: true,\n });\n\n // Validate form\n if (!validateForm()) {\n return;\n }\n\n try {\n await login({\n email: formData.email.trim(),\n password: formData.password,\n });\n onSuccess?.();\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"Login failed\";\n setErrors(prev => ({ ...prev, general: errorMessage }));\n onError?.(errorMessage);\n }\n },\n [formData, validateForm, login, onSuccess, onError]\n );\n\n // Check if form is valid\n const isFormValidState = !errors.email && !errors.password && formData.email && formData.password;\n\n return (\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/LoginForm/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/PasswordResetForm/PasswordResetForm.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ForgotPasswordRequest' is defined but never used.","line":14,"column":15,"nodeType":null,"messageId":"unusedVar","endLine":14,"endColumn":36},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ResetPasswordRequest' is defined but never used.","line":14,"column":38,"nodeType":null,"messageId":"unusedVar","endLine":14,"endColumn":58},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":83,"column":28,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":83,"endColumn":31,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2287,2290],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2287,2290],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-argument","severity":2,"message":"Unsafe argument of type `any` assigned to a parameter of type `string`.","line":94,"column":36,"nodeType":"Identifier","messageId":"unsafeArgument","endLine":94,"endColumn":41},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useCallback has a missing dependency: 'validationConfig'. Either include it or remove the dependency array.","line":97,"column":5,"nodeType":"ArrayExpression","endLine":97,"endColumn":25,"suggestions":[{"desc":"Update the dependencies array to be: [resetData.password, validationConfig]","fix":{"range":[2777,2797],"text":"[resetData.password, validationConfig]"}}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":102,"column":28,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":102,"endColumn":31,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2897,2900],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2897,2900],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-assignment","severity":2,"message":"Unsafe assignment of an `any` value.","line":104,"column":44,"nodeType":"Property","messageId":"anyAssignment","endLine":104,"endColumn":58},{"ruleId":"@typescript-eslint/no-unsafe-assignment","severity":2,"message":"Unsafe assignment of an `any` value.","line":106,"column":42,"nodeType":"Property","messageId":"anyAssignment","endLine":106,"endColumn":56},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `'`, `‘`, `'`, `’`.","line":252,"column":15,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[7393,7442],"text":"\n We've sent a password reset link to "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"‘"},"fix":{"range":[7393,7442],"text":"\n We‘ve sent a password reset link to "},"desc":"Replace with `‘`."},{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[7393,7442],"text":"\n We've sent a password reset link to "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"’"},"fix":{"range":[7393,7442],"text":"\n We’ve sent a password reset link to "},"desc":"Replace with `’`."}]},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `'`, `‘`, `'`, `’`.","line":258,"column":17,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[7593,7679],"text":"\n Didn't receive the email? Check your spam folder or try again.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"‘"},"fix":{"range":[7593,7679],"text":"\n Didn‘t receive the email? Check your spam folder or try again.\n "},"desc":"Replace with `‘`."},{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[7593,7679],"text":"\n Didn't receive the email? Check your spam folder or try again.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"’"},"fix":{"range":[7593,7679],"text":"\n Didn’t receive the email? Check your spam folder or try again.\n "},"desc":"Replace with `’`."}]},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":289,"column":20,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":289,"endColumn":34},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `'`, `‘`, `'`, `’`.","line":303,"column":46,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[8862,8964],"text":"\n Enter your email address and we'll send you a link to reset your password.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"‘"},"fix":{"range":[8862,8964],"text":"\n Enter your email address and we‘ll send you a link to reset your password.\n "},"desc":"Replace with `‘`."},{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[8862,8964],"text":"\n Enter your email address and we'll send you a link to reset your password.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"’"},"fix":{"range":[8862,8964],"text":"\n Enter your email address and we’ll send you a link to reset your password.\n "},"desc":"Replace with `’`."}]}],"suppressedMessages":[],"errorCount":9,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Password Reset Form Component\n * Form for requesting and resetting passwords\n */\n\n\"use client\";\n\nimport { useState, useCallback } from \"react\";\nimport Link from \"next/link\";\nimport { Button, Input, ErrorMessage } from \"@/components/ui\";\nimport { FormField } from \"@/components/common/FormField\";\nimport { usePasswordReset } from \"../../hooks/use-auth\";\nimport { validationRules, validateField } from \"@/lib/form-validation\";\nimport type { ForgotPasswordRequest, ResetPasswordRequest } from \"@/lib/types\";\n\ninterface PasswordResetFormProps {\n mode: \"request\" | \"reset\";\n token?: string;\n onSuccess?: () => void;\n onError?: (error: string) => void;\n showLoginLink?: boolean;\n className?: string;\n}\n\ninterface RequestFormData {\n email: string;\n}\n\ninterface ResetFormData {\n password: string;\n confirmPassword: string;\n}\n\ninterface FormErrors {\n email?: string;\n password?: string;\n confirmPassword?: string;\n general?: string;\n}\n\nexport function PasswordResetForm({\n mode,\n token,\n onSuccess,\n onError,\n showLoginLink = true,\n className = \"\",\n}: PasswordResetFormProps) {\n const { requestPasswordReset, resetPassword, loading, error, clearError } = usePasswordReset();\n\n const [requestData, setRequestData] = useState({\n email: \"\",\n });\n\n const [resetData, setResetData] = useState({\n password: \"\",\n confirmPassword: \"\",\n });\n\n const [errors, setErrors] = useState({});\n const [touched, setTouched] = useState>({});\n const [isSubmitted, setIsSubmitted] = useState(false);\n\n // Validation rules\n const validationConfig = {\n email: [\n validationRules.required(\"Email is required\"),\n validationRules.email(\"Please enter a valid email address\"),\n ],\n password: [\n validationRules.required(\"Password is required\"),\n validationRules.minLength(8, \"Password must be at least 8 characters\"),\n validationRules.pattern(\n /^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)/,\n \"Password must contain at least one uppercase letter, one lowercase letter, and one number\"\n ),\n ],\n confirmPassword: [validationRules.required(\"Please confirm your password\")],\n };\n\n // Validate field\n const validateFormField = useCallback(\n (field: string, value: any) => {\n const rules = validationConfig[field as keyof typeof validationConfig];\n if (!rules) return null;\n\n // Special validation for confirm password\n if (field === \"confirmPassword\") {\n if (!value) return \"Please confirm your password\";\n if (value !== resetData.password) return \"Passwords do not match\";\n return null;\n }\n\n const result = validateField(value, rules);\n return result.isValid ? null : result.errors[0];\n },\n [resetData.password]\n );\n\n // Handle field change\n const handleFieldChange = useCallback(\n (field: string, value: any) => {\n if (mode === \"request\") {\n setRequestData(prev => ({ ...prev, [field]: value }));\n } else {\n setResetData(prev => ({ ...prev, [field]: value }));\n }\n\n // Clear general error when user starts typing\n if (errors.general) {\n setErrors(prev => ({ ...prev, general: undefined }));\n clearError();\n }\n\n // Validate field if it has been touched\n if (touched[field]) {\n const fieldError = validateFormField(field, value);\n setErrors(prev => ({ ...prev, [field]: fieldError || undefined }));\n }\n\n // Also validate confirm password when password changes\n if (field === \"password\" && touched.confirmPassword && mode === \"reset\") {\n const confirmPasswordError = validateFormField(\n \"confirmPassword\",\n resetData.confirmPassword\n );\n setErrors(prev => ({ ...prev, confirmPassword: confirmPasswordError || undefined }));\n }\n },\n [mode, errors.general, touched, validateFormField, clearError, resetData.confirmPassword]\n );\n\n // Handle field blur\n const handleFieldBlur = useCallback(\n (field: string) => {\n setTouched(prev => ({ ...prev, [field]: true }));\n\n const value =\n mode === \"request\"\n ? requestData[field as keyof RequestFormData]\n : resetData[field as keyof ResetFormData];\n const fieldError = validateFormField(field, value);\n setErrors(prev => ({ ...prev, [field]: fieldError || undefined }));\n },\n [mode, requestData, resetData, validateFormField]\n );\n\n // Validate form\n const validateForm = useCallback(() => {\n const newErrors: FormErrors = {};\n let isValid = true;\n\n if (mode === \"request\") {\n const emailError = validateFormField(\"email\", requestData.email);\n if (emailError) {\n newErrors.email = emailError;\n isValid = false;\n }\n } else {\n const passwordError = validateFormField(\"password\", resetData.password);\n if (passwordError) {\n newErrors.password = passwordError;\n isValid = false;\n }\n\n const confirmPasswordError = validateFormField(\"confirmPassword\", resetData.confirmPassword);\n if (confirmPasswordError) {\n newErrors.confirmPassword = confirmPasswordError;\n isValid = false;\n }\n }\n\n setErrors(newErrors);\n return isValid;\n }, [mode, requestData, resetData, validateFormField]);\n\n // Handle form submission\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n\n // Mark all fields as touched\n if (mode === \"request\") {\n setTouched({ email: true });\n } else {\n setTouched({ password: true, confirmPassword: true });\n }\n\n // Validate form\n if (!validateForm()) {\n return;\n }\n\n try {\n if (mode === \"request\") {\n await requestPasswordReset({ email: requestData.email.trim() });\n setIsSubmitted(true);\n } else {\n if (!token) {\n throw new Error(\"Reset token is required\");\n }\n\n await resetPassword({\n token,\n password: resetData.password,\n confirmPassword: resetData.confirmPassword,\n });\n }\n\n onSuccess?.();\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"Operation failed\";\n setErrors(prev => ({ ...prev, general: errorMessage }));\n onError?.(errorMessage);\n }\n },\n [\n mode,\n token,\n requestData,\n resetData,\n validateForm,\n requestPasswordReset,\n resetPassword,\n onSuccess,\n onError,\n ]\n );\n\n // Show success message for request mode\n if (mode === \"request\" && isSubmitted) {\n return (\n
\n
\n
\n \n
\n
Check your email
\n
\n We've sent a password reset link to {requestData.email}\n
\n
\n\n
\n
\n Didn't receive the email? Check your spam folder or try again.\n
\n\n \n
\n\n {showLoginLink && (\n
\n \n Back to Sign In\n \n
\n )}\n
\n );\n }\n\n return (\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/PasswordResetForm/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/SetPasswordForm/SetPasswordForm.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":77,"column":36,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":77,"endColumn":39,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2080,2083],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2080,2083],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-argument","severity":2,"message":"Unsafe argument of type `any` assigned to a parameter of type `string`.","line":88,"column":36,"nodeType":"Identifier","messageId":"unsafeArgument","endLine":88,"endColumn":41},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useCallback has a missing dependency: 'validationConfig'. Either include it or remove the dependency array.","line":91,"column":5,"nodeType":"ArrayExpression","endLine":91,"endColumn":24,"suggestions":[{"desc":"Update the dependencies array to be: [formData.password, validationConfig]","fix":{"range":[2569,2588],"text":"[formData.password, validationConfig]"}}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":96,"column":36,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":96,"endColumn":39,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2696,2699],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2696,2699],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-assignment","severity":2,"message":"Unsafe assignment of an `any` value.","line":97,"column":39,"nodeType":"Property","messageId":"anyAssignment","endLine":97,"endColumn":53},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":207,"column":22,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":207,"endColumn":36}],"suppressedMessages":[],"errorCount":5,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Set Password Form Component\n * Form for setting password after WHMCS account linking\n */\n\n\"use client\";\n\nimport { useState, useCallback } from \"react\";\nimport Link from \"next/link\";\nimport { Button, Input, ErrorMessage } from \"@/components/ui\";\nimport { FormField } from \"@/components/common/FormField\";\nimport { useWhmcsLink } from \"../../hooks/use-auth\";\nimport { validationRules, validateField } from \"@/lib/form-validation\";\n\ninterface SetPasswordFormProps {\n email?: string;\n onSuccess?: () => void;\n onError?: (error: string) => void;\n showLoginLink?: boolean;\n className?: string;\n}\n\ninterface FormData {\n email: string;\n password: string;\n confirmPassword: string;\n}\n\ninterface FormErrors {\n email?: string;\n password?: string;\n confirmPassword?: string;\n general?: string;\n}\n\nexport function SetPasswordForm({\n email: initialEmail = \"\",\n onSuccess,\n onError,\n showLoginLink = true,\n className = \"\",\n}: SetPasswordFormProps) {\n const { setPassword, loading, error, clearError } = useWhmcsLink();\n\n const [formData, setFormData] = useState({\n email: initialEmail,\n password: \"\",\n confirmPassword: \"\",\n });\n\n const [errors, setErrors] = useState({});\n const [touched, setTouched] = useState>({\n email: false,\n password: false,\n confirmPassword: false,\n });\n\n // Validation rules\n const validationConfig = {\n email: [\n validationRules.required(\"Email is required\"),\n validationRules.email(\"Please enter a valid email address\"),\n ],\n password: [\n validationRules.required(\"Password is required\"),\n validationRules.minLength(8, \"Password must be at least 8 characters\"),\n validationRules.pattern(\n /^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)/,\n \"Password must contain at least one uppercase letter, one lowercase letter, and one number\"\n ),\n ],\n confirmPassword: [validationRules.required(\"Please confirm your password\")],\n };\n\n // Validate field\n const validateFormField = useCallback(\n (field: keyof FormData, value: any) => {\n const rules = validationConfig[field as keyof typeof validationConfig];\n if (!rules) return null;\n\n // Special validation for confirm password\n if (field === \"confirmPassword\") {\n if (!value) return \"Please confirm your password\";\n if (value !== formData.password) return \"Passwords do not match\";\n return null;\n }\n\n const result = validateField(value, rules);\n return result.isValid ? null : result.errors[0];\n },\n [formData.password]\n );\n\n // Handle field change\n const handleFieldChange = useCallback(\n (field: keyof FormData, value: any) => {\n setFormData(prev => ({ ...prev, [field]: value }));\n\n // Clear general error when user starts typing\n if (errors.general) {\n setErrors(prev => ({ ...prev, general: undefined }));\n clearError();\n }\n\n // Validate field if it has been touched\n if (touched[field]) {\n const fieldError = validateFormField(field, value);\n setErrors(prev => ({ ...prev, [field]: fieldError || undefined }));\n }\n\n // Also validate confirm password when password changes\n if (field === \"password\" && touched.confirmPassword) {\n const confirmPasswordError = validateFormField(\"confirmPassword\", formData.confirmPassword);\n setErrors(prev => ({ ...prev, confirmPassword: confirmPasswordError || undefined }));\n }\n },\n [errors.general, touched, validateFormField, clearError, formData.confirmPassword]\n );\n\n // Handle field blur\n const handleFieldBlur = useCallback(\n (field: keyof FormData) => {\n setTouched(prev => ({ ...prev, [field]: true }));\n\n const fieldError = validateFormField(field, formData[field]);\n setErrors(prev => ({ ...prev, [field]: fieldError || undefined }));\n },\n [formData, validateFormField]\n );\n\n // Validate entire form\n const validateForm = useCallback(() => {\n const newErrors: FormErrors = {};\n let isValid = true;\n\n // Validate email\n const emailError = validateFormField(\"email\", formData.email);\n if (emailError) {\n newErrors.email = emailError;\n isValid = false;\n }\n\n // Validate password\n const passwordError = validateFormField(\"password\", formData.password);\n if (passwordError) {\n newErrors.password = passwordError;\n isValid = false;\n }\n\n // Validate confirm password\n const confirmPasswordError = validateFormField(\"confirmPassword\", formData.confirmPassword);\n if (confirmPasswordError) {\n newErrors.confirmPassword = confirmPasswordError;\n isValid = false;\n }\n\n setErrors(newErrors);\n return isValid;\n }, [formData, validateFormField]);\n\n // Handle form submission\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n\n // Mark all fields as touched\n setTouched({\n email: true,\n password: true,\n confirmPassword: true,\n });\n\n // Validate form\n if (!validateForm()) {\n return;\n }\n\n try {\n await setPassword(formData.email.trim(), formData.password);\n onSuccess?.();\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"Failed to set password\";\n setErrors(prev => ({ ...prev, general: errorMessage }));\n onError?.(errorMessage);\n }\n },\n [formData, validateForm, setPassword, onSuccess, onError]\n );\n\n // Check if form is valid\n const isFormValid =\n !errors.email &&\n !errors.password &&\n !errors.confirmPassword &&\n formData.email &&\n formData.password &&\n formData.confirmPassword;\n\n return (\n
\n {/* Header */}\n
\n
Set Your Password
\n
Complete your account setup by creating a secure password.
\n
\n\n \n\n {/* Login Link */}\n {showLoginLink && (\n
\n Already have a password? \n \n Sign in\n \n
\n )}\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/SetPasswordForm/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/SignupForm/AccountStep.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/SignupForm/AddressStep.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/SignupForm/MultiStepForm.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/SignupForm/PersonalStep.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/SignupForm/PreferencesStep.tsx","messages":[{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`\"` can be escaped with `"`, `“`, `"`, `”`.","line":79,"column":23,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"""},"fix":{"range":[2556,2697],"text":"\n By clicking "Create Account\", you'll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `"`."},{"messageId":"replaceWithAlt","data":{"alt":"“"},"fix":{"range":[2556,2697],"text":"\n By clicking “Create Account\", you'll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `“`."},{"messageId":"replaceWithAlt","data":{"alt":"""},"fix":{"range":[2556,2697],"text":"\n By clicking "Create Account\", you'll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `"`."},{"messageId":"replaceWithAlt","data":{"alt":"”"},"fix":{"range":[2556,2697],"text":"\n By clicking ”Create Account\", you'll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `”`."}]},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`\"` can be escaped with `"`, `“`, `"`, `”`.","line":79,"column":38,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"""},"fix":{"range":[2556,2697],"text":"\n By clicking \"Create Account", you'll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `"`."},{"messageId":"replaceWithAlt","data":{"alt":"“"},"fix":{"range":[2556,2697],"text":"\n By clicking \"Create Account“, you'll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `“`."},{"messageId":"replaceWithAlt","data":{"alt":"""},"fix":{"range":[2556,2697],"text":"\n By clicking \"Create Account", you'll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `"`."},{"messageId":"replaceWithAlt","data":{"alt":"”"},"fix":{"range":[2556,2697],"text":"\n By clicking \"Create Account”, you'll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `”`."}]},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `'`, `‘`, `'`, `’`.","line":79,"column":44,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[2556,2697],"text":"\n By clicking \"Create Account\", you'll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"‘"},"fix":{"range":[2556,2697],"text":"\n By clicking \"Create Account\", you‘ll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `‘`."},{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[2556,2697],"text":"\n By clicking \"Create Account\", you'll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"’"},"fix":{"range":[2556,2697],"text":"\n By clicking \"Create Account\", you’ll be able to access your dashboard and start using our\n services immediately.\n "},"desc":"Replace with `’`."}]}],"suppressedMessages":[],"errorCount":3,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Preferences Step Component\n * Terms acceptance and marketing preferences\n */\n\n\"use client\";\n\nimport Link from \"next/link\";\n\ninterface PreferencesStepProps {\n formData: {\n acceptTerms: boolean;\n marketingConsent: boolean;\n };\n errors: {\n acceptTerms?: string;\n };\n onFieldChange: (field: string, value: boolean) => void;\n onFieldBlur: (field: string) => void;\n loading?: boolean;\n}\n\nexport function PreferencesStep({\n formData,\n errors,\n onFieldChange,\n onFieldBlur,\n loading = false,\n}: PreferencesStepProps) {\n return (\n
\n By clicking \"Create Account\", you'll be able to access your dashboard and start using our\n services immediately.\n
\n
\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/auth/components/SignupForm/SignupForm.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":126,"column":27,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":126,"endColumn":30,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3703,3706],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3703,3706],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":128,"column":12,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":128,"endColumn":15,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3799,3802],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3799,3802],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":133,"column":32,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":133,"endColumn":35,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3870,3873],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3870,3873],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-return","severity":2,"message":"Unsafe return of a value of type `any`.","line":134,"column":5,"nodeType":"ReturnStatement","messageId":"unsafeReturn","endLine":134,"endColumn":74},{"ruleId":"@typescript-eslint/no-unsafe-return","severity":2,"message":"Unsafe return of a value of type `any`.","line":134,"column":53,"nodeType":"ChainExpression","messageId":"unsafeReturn","endLine":134,"endColumn":67},{"ruleId":"@typescript-eslint/no-unsafe-member-access","severity":2,"message":"Unsafe member access [key] on an `any` value.","line":134,"column":63,"nodeType":"Identifier","messageId":"unsafeMemberExpression","endLine":134,"endColumn":66},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":138,"column":32,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":138,"endColumn":35,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4027,4030],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4027,4030],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":138,"column":58,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":138,"endColumn":61,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4053,4056],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4053,4056],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-assignment","severity":2,"message":"Unsafe assignment of an `any` value.","line":141,"column":11,"nodeType":"VariableDeclarator","messageId":"anyAssignment","endLine":144,"endColumn":12},{"ruleId":"@typescript-eslint/no-unsafe-member-access","severity":2,"message":"Unsafe member access [key] on an `any` value.","line":142,"column":20,"nodeType":"Identifier","messageId":"unsafeMemberExpression","endLine":142,"endColumn":23},{"ruleId":"@typescript-eslint/no-unsafe-member-access","severity":2,"message":"Unsafe member access [key] on an `any` value.","line":142,"column":34,"nodeType":"Identifier","messageId":"unsafeMemberExpression","endLine":142,"endColumn":37},{"ruleId":"@typescript-eslint/no-unsafe-return","severity":2,"message":"Unsafe return of a value of type `any`.","line":143,"column":7,"nodeType":"ReturnStatement","messageId":"unsafeReturn","endLine":143,"endColumn":27},{"ruleId":"@typescript-eslint/no-unsafe-member-access","severity":2,"message":"Unsafe member access [key] on an `any` value.","line":143,"column":22,"nodeType":"Identifier","messageId":"unsafeMemberExpression","endLine":143,"endColumn":25},{"ruleId":"@typescript-eslint/no-unsafe-assignment","severity":2,"message":"Unsafe assignment of an `any` value.","line":145,"column":5,"nodeType":"AssignmentExpression","messageId":"anyAssignment","endLine":145,"endColumn":28},{"ruleId":"@typescript-eslint/no-unsafe-member-access","severity":2,"message":"Unsafe member access [lastKey] on an `any` value.","line":145,"column":12,"nodeType":"Identifier","messageId":"unsafeMemberExpression","endLine":145,"endColumn":19},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":150,"column":28,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":150,"endColumn":31,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4388,4391],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4388,4391],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-argument","severity":2,"message":"Unsafe argument of type `any` assigned to a parameter of type `string`.","line":161,"column":36,"nodeType":"Identifier","messageId":"unsafeArgument","endLine":161,"endColumn":41},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useCallback has a missing dependency: 'validationConfig'. Either include it or remove the dependency array.","line":164,"column":5,"nodeType":"ArrayExpression","endLine":164,"endColumn":24,"suggestions":[{"desc":"Update the dependencies array to be: [formData.password, validationConfig]","fix":{"range":[4877,4896],"text":"[formData.password, validationConfig]"}}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":169,"column":28,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":169,"endColumn":31,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4996,4999],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4996,4999],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-assignment","severity":2,"message":"Unsafe assignment of an `any` value.","line":175,"column":11,"nodeType":"AssignmentExpression","messageId":"anyAssignment","endLine":175,"endColumn":42},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":175,"column":23,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":175,"endColumn":26,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5194,5197],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5194,5197],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-member-access","severity":2,"message":"Unsafe member access [field] on an `any` value.","line":175,"column":28,"nodeType":"Identifier","messageId":"unsafeMemberExpression","endLine":175,"endColumn":33},{"ruleId":"@typescript-eslint/no-unsafe-assignment","severity":2,"message":"Unsafe assignment of an `any` value.","line":206,"column":13,"nodeType":"VariableDeclarator","messageId":"anyAssignment","endLine":208,"endColumn":35},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":208,"column":24,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":208,"endColumn":27,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6334,6337],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6334,6337],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-member-access","severity":2,"message":"Unsafe member access [field] on an `any` value.","line":208,"column":29,"nodeType":"Identifier","messageId":"unsafeMemberExpression","endLine":208,"endColumn":34},{"ruleId":"@typescript-eslint/no-unsafe-assignment","severity":2,"message":"Unsafe assignment of an `any` value.","line":230,"column":15,"nodeType":"VariableDeclarator","messageId":"anyAssignment","endLine":232,"endColumn":37},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":232,"column":26,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":232,"endColumn":29,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7184,7187],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7184,7187],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unsafe-member-access","severity":2,"message":"Unsafe member access [field] on an `any` value.","line":232,"column":31,"nodeType":"Identifier","messageId":"unsafeMemberExpression","endLine":232,"endColumn":36},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":373,"column":18,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":373,"endColumn":32}],"suppressedMessages":[],"errorCount":28,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Signup Form Component\n * Refactored multi-step signup form using smaller components\n */\n\n\"use client\";\n\nimport { useState, useCallback } from \"react\";\nimport Link from \"next/link\";\nimport { ErrorMessage } from \"@/components/ui\";\nimport { useSignup } from \"../../hooks/use-auth\";\nimport { validationRules, validateField } from \"@/lib/form-validation\";\nimport type { SignupData } from \"@/lib/auth/api\";\n\nimport { MultiStepForm, type FormStep } from \"./MultiStepForm\";\nimport { AccountStep } from \"./AccountStep\";\nimport { PersonalStep } from \"./PersonalStep\";\nimport { AddressStep } from \"./AddressStep\";\nimport { PreferencesStep } from \"./PreferencesStep\";\n\ninterface SignupFormProps {\n onSuccess?: () => void;\n onError?: (error: string) => void;\n showLoginLink?: boolean;\n className?: string;\n}\n\ninterface SignupFormData {\n email: string;\n password: string;\n confirmPassword: string;\n firstName: string;\n lastName: string;\n company?: string;\n phone?: string;\n sfNumber: string;\n address: {\n line1: string;\n line2?: string;\n city: string;\n state: string;\n postalCode: string;\n country: string;\n };\n nationality?: string;\n dateOfBirth?: string;\n gender?: \"male\" | \"female\" | \"other\";\n acceptTerms: boolean;\n marketingConsent: boolean;\n}\n\ninterface FormErrors {\n [key: string]: string | undefined;\n}\n\nexport function SignupForm({\n onSuccess,\n onError,\n showLoginLink = true,\n className = \"\",\n}: SignupFormProps) {\n const { signup, loading, error, clearError } = useSignup();\n\n const [formData, setFormData] = useState({\n email: \"\",\n password: \"\",\n confirmPassword: \"\",\n firstName: \"\",\n lastName: \"\",\n company: \"\",\n phone: \"\",\n sfNumber: \"\",\n address: {\n line1: \"\",\n line2: \"\",\n city: \"\",\n state: \"\",\n postalCode: \"\",\n country: \"US\",\n },\n nationality: \"\",\n dateOfBirth: \"\",\n gender: undefined,\n acceptTerms: false,\n marketingConsent: false,\n });\n\n const [errors, setErrors] = useState({});\n const [touched, setTouched] = useState>({});\n const [currentStepIndex, setCurrentStepIndex] = useState(0);\n\n // Validation rules\n const validationConfig = {\n email: [\n validationRules.required(\"Email is required\"),\n validationRules.email(\"Please enter a valid email address\"),\n ],\n password: [\n validationRules.required(\"Password is required\"),\n validationRules.minLength(8, \"Password must be at least 8 characters\"),\n validationRules.pattern(\n /^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)/,\n \"Password must contain at least one uppercase letter, one lowercase letter, and one number\"\n ),\n ],\n confirmPassword: [validationRules.required(\"Please confirm your password\")],\n firstName: [\n validationRules.required(\"First name is required\"),\n validationRules.minLength(2, \"First name must be at least 2 characters\"),\n ],\n lastName: [\n validationRules.required(\"Last name is required\"),\n validationRules.minLength(2, \"Last name must be at least 2 characters\"),\n ],\n sfNumber: [\n validationRules.required(\"SF Number is required\"),\n validationRules.minLength(6, \"SF Number must be at least 6 characters\"),\n ],\n \"address.line1\": [validationRules.required(\"Address line 1 is required\")],\n \"address.city\": [validationRules.required(\"City is required\")],\n \"address.state\": [validationRules.required(\"State/Province is required\")],\n \"address.postalCode\": [validationRules.required(\"Postal code is required\")],\n \"address.country\": [validationRules.required(\"Country is required\")],\n acceptTerms: [\n {\n validate: (value: any) => value === true,\n message: \"You must accept the terms and conditions\",\n } as any,\n ],\n };\n\n // Get nested value\n const getNestedValue = (obj: any, path: string) => {\n return path.split(\".\").reduce((current, key) => current?.[key], obj);\n };\n\n // Set nested value\n const setNestedValue = (obj: any, path: string, value: any) => {\n const keys = path.split(\".\");\n const lastKey = keys.pop()!;\n const target = keys.reduce((current, key) => {\n if (!current[key]) current[key] = {};\n return current[key];\n }, obj);\n target[lastKey] = value;\n };\n\n // Validate field\n const validateFormField = useCallback(\n (field: string, value: any) => {\n const rules = validationConfig[field as keyof typeof validationConfig];\n if (!rules) return null;\n\n // Special validation for confirm password\n if (field === \"confirmPassword\") {\n if (!value) return \"Please confirm your password\";\n if (value !== formData.password) return \"Passwords do not match\";\n return null;\n }\n\n const result = validateField(value, rules);\n return result.isValid ? null : result.errors[0];\n },\n [formData.password]\n );\n\n // Handle field change\n const handleFieldChange = useCallback(\n (field: string, value: any) => {\n setFormData(prev => {\n const newData = { ...prev };\n if (field.includes(\".\")) {\n setNestedValue(newData, field, value);\n } else {\n (newData as any)[field] = value;\n }\n return newData;\n });\n\n // Clear general error when user starts typing\n if (errors.general) {\n setErrors(prev => ({ ...prev, general: undefined }));\n clearError();\n }\n\n // Validate field if it has been touched\n if (touched[field]) {\n const fieldError = validateFormField(field, value);\n setErrors(prev => ({ ...prev, [field]: fieldError || undefined }));\n }\n\n // Also validate confirm password when password changes\n if (field === \"password\" && touched.confirmPassword) {\n const confirmPasswordError = validateFormField(\"confirmPassword\", formData.confirmPassword);\n setErrors(prev => ({ ...prev, confirmPassword: confirmPasswordError || undefined }));\n }\n },\n [errors.general, touched, validateFormField, clearError, formData.confirmPassword]\n );\n\n // Handle field blur\n const handleFieldBlur = useCallback(\n (field: string) => {\n setTouched(prev => ({ ...prev, [field]: true }));\n\n const fieldValue = field.includes(\".\")\n ? getNestedValue(formData, field)\n : (formData as any)[field];\n const fieldError = validateFormField(field, fieldValue);\n setErrors(prev => ({ ...prev, [field]: fieldError || undefined }));\n },\n [formData, validateFormField]\n );\n\n // Validate step\n const validateStep = useCallback(\n (stepIndex: number) => {\n const stepFields = [\n [\"email\", \"password\", \"confirmPassword\"], // Account\n [\"firstName\", \"lastName\", \"sfNumber\"], // Personal\n [\"address.line1\", \"address.city\", \"address.state\", \"address.postalCode\", \"address.country\"], // Address\n [\"acceptTerms\"], // Preferences\n ];\n\n const fields = stepFields[stepIndex];\n const stepErrors: FormErrors = {};\n let isValid = true;\n\n fields.forEach(field => {\n const fieldValue = field.includes(\".\")\n ? getNestedValue(formData, field)\n : (formData as any)[field];\n const fieldError = validateFormField(field, fieldValue);\n if (fieldError) {\n stepErrors[field] = fieldError;\n isValid = false;\n }\n });\n\n setErrors(prev => ({ ...prev, ...stepErrors }));\n return isValid;\n },\n [formData, validateFormField]\n );\n\n // Handle form submission\n const handleSubmit = useCallback(async () => {\n // Validate all steps\n const allValid = [0, 1, 2, 3].every(stepIndex => validateStep(stepIndex));\n\n if (!allValid) {\n return;\n }\n\n try {\n const signupData: SignupData = {\n email: formData.email.trim(),\n password: formData.password,\n firstName: formData.firstName.trim(),\n lastName: formData.lastName.trim(),\n company: formData.company?.trim() || undefined,\n phone: formData.phone?.trim() || undefined,\n sfNumber: formData.sfNumber.trim(),\n address: {\n line1: formData.address.line1.trim(),\n line2: formData.address.line2?.trim() || undefined,\n city: formData.address.city.trim(),\n state: formData.address.state.trim(),\n postalCode: formData.address.postalCode.trim(),\n country: formData.address.country,\n },\n nationality: formData.nationality?.trim() || undefined,\n dateOfBirth: formData.dateOfBirth || undefined,\n gender: formData.gender || undefined,\n };\n\n await signup(signupData);\n onSuccess?.();\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : \"Signup failed\";\n setErrors(prev => ({ ...prev, general: errorMessage }));\n onError?.(errorMessage);\n }\n }, [formData, validateStep, signup, onSuccess, onError]);\n\n // Handle step change\n const handleStepChange = useCallback(\n (stepIndex: number) => {\n setCurrentStepIndex(stepIndex);\n // Validate current step when moving to next\n if (stepIndex > currentStepIndex) {\n validateStep(currentStepIndex);\n }\n },\n [currentStepIndex, validateStep]\n );\n\n // Define steps\n const steps: FormStep[] = [\n {\n key: \"account\",\n title: \"Account Details\",\n description: \"Create your account credentials\",\n component: (\n \n ),\n isValid: validateStep(0),\n },\n {\n key: \"personal\",\n title: \"Personal Information\",\n description: \"Tell us about yourself\",\n component: (\n \n ),\n isValid: validateStep(1),\n },\n {\n key: \"address\",\n title: \"Address & Details\",\n description: \"Your address and additional information\",\n component: (\n \n ),\n isValid: validateStep(2),\n },\n {\n key: \"preferences\",\n title: \"Preferences\",\n description: \"Set your preferences\",\n component: (\n \n ),\n isValid: validateStep(3),\n },\n ];\n\n return (\n
\n \n \n );\n}\n\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/components/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/components/internet/InstallationOptions.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/components/internet/InternetConfigureView.tsx","messages":[{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`\"` can be escaped with `"`, `“`, `"`, `”`.","line":185,"column":57,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"""},"fix":{"range":[6708,6875],"text":"\n * Will appear on the invoice as "Platinum Base Plan\". Device subscriptions\n will be added later.\n "},"desc":"Replace with `"`."},{"messageId":"replaceWithAlt","data":{"alt":"“"},"fix":{"range":[6708,6875],"text":"\n * Will appear on the invoice as “Platinum Base Plan\". Device subscriptions\n will be added later.\n "},"desc":"Replace with `“`."},{"messageId":"replaceWithAlt","data":{"alt":"""},"fix":{"range":[6708,6875],"text":"\n * Will appear on the invoice as "Platinum Base Plan\". Device subscriptions\n will be added later.\n "},"desc":"Replace with `"`."},{"messageId":"replaceWithAlt","data":{"alt":"”"},"fix":{"range":[6708,6875],"text":"\n * Will appear on the invoice as ”Platinum Base Plan\". Device subscriptions\n will be added later.\n "},"desc":"Replace with `”`."}]},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`\"` can be escaped with `"`, `“`, `"`, `”`.","line":185,"column":76,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"""},"fix":{"range":[6708,6875],"text":"\n * Will appear on the invoice as \"Platinum Base Plan". Device subscriptions\n will be added later.\n "},"desc":"Replace with `"`."},{"messageId":"replaceWithAlt","data":{"alt":"“"},"fix":{"range":[6708,6875],"text":"\n * Will appear on the invoice as \"Platinum Base Plan“. Device subscriptions\n will be added later.\n "},"desc":"Replace with `“`."},{"messageId":"replaceWithAlt","data":{"alt":"""},"fix":{"range":[6708,6875],"text":"\n * Will appear on the invoice as \"Platinum Base Plan". Device subscriptions\n will be added later.\n "},"desc":"Replace with `"`."},{"messageId":"replaceWithAlt","data":{"alt":"”"},"fix":{"range":[6708,6875],"text":"\n * Will appear on the invoice as \"Platinum Base Plan”. Device subscriptions\n will be added later.\n "},"desc":"Replace with `”`."}]}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { PageLayout } from \"@/components/layout/PageLayout\";\nimport { LoadingSpinner } from \"@/components/ui/loading-spinner\";\nimport { AnimatedCard } from \"@/components/ui\";\nimport { Button } from \"@/components/ui/button\";\nimport { ProgressSteps, StepHeader } from \"@/components/ui\";\nimport { AddonGroup } from \"@/features/catalog/components/base/AddonGroup\";\nimport { InstallationOptions } from \"@/features/catalog/components/internet/InstallationOptions\";\nimport { ServerIcon, ArrowLeftIcon, ArrowRightIcon } from \"@heroicons/react/24/outline\";\nimport type {\n InternetPlan,\n InternetAddon,\n InternetInstallation,\n} from \"@/shared/types/catalog.types\";\nimport type { AccessMode } from \"../../hooks/useConfigureParams\";\n\ntype Props = {\n plan: InternetPlan | null;\n loading: boolean;\n addons: InternetAddon[];\n installations: InternetInstallation[];\n\n mode: AccessMode | null;\n setMode: (mode: AccessMode) => void;\n installPlan: string | null;\n setInstallPlan: (type: string | null) => void;\n selectedAddonSkus: string[];\n setSelectedAddonSkus: (skus: string[]) => void;\n\n currentStep: number;\n isTransitioning: boolean;\n transitionToStep: (nextStep: number) => void;\n\n monthlyTotal: number;\n oneTimeTotal: number;\n\n onConfirm: () => void;\n};\n\nexport function InternetConfigureView({\n plan,\n loading,\n addons,\n installations,\n mode,\n setMode,\n installPlan,\n setInstallPlan,\n selectedAddonSkus,\n setSelectedAddonSkus,\n currentStep,\n isTransitioning,\n transitionToStep,\n monthlyTotal,\n oneTimeTotal,\n onConfirm,\n}: Props) {\n const handleAddonSelection = (newSelectedSkus: string[]) => setSelectedAddonSkus(newSelectedSkus);\n\n if (loading) {\n return (\n }\n title=\"Configure Internet Service\"\n description=\"Set up your internet service options\"\n >\n
\n \n
\n \n );\n }\n\n if (!plan) {\n return (\n }\n title=\"Configure Internet Service\"\n description=\"Set up your internet service options\"\n >\n
\n Personalized recommendations based on your location and account eligibility.\n
\n
\n
\n } title=\"Location-Based Plans\" description=\"Internet plans tailored to your house type and infrastructure\" />\n } title=\"Seamless Integration\" description=\"Manage all services from a single account\" />\n
\n
\n
\n \n );\n}\n\nexport default CatalogHomeContainer;\n\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/containers/InternetConfigure.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/containers/InternetPlans.tsx","messages":[{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"The 'plans' logical expression could make the dependencies of useEffect Hook (at line 30) change on every render. To fix this, wrap the initialization of 'plans' in its own useMemo() Hook.","line":22,"column":9,"nodeType":"VariableDeclarator","endLine":22,"endColumn":34},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `'`, `‘`, `'`, `’`.","line":148,"column":24,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[5836,5941],"text":"\n We couldn't find any internet plans available for your location at this time.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"‘"},"fix":{"range":[5836,5941],"text":"\n We couldn‘t find any internet plans available for your location at this time.\n "},"desc":"Replace with `‘`."},{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[5836,5941],"text":"\n We couldn't find any internet plans available for your location at this time.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"’"},"fix":{"range":[5836,5941],"text":"\n We couldn’t find any internet plans available for your location at this time.\n "},"desc":"Replace with `’`."}]},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `'`, `‘`, `'`, `’`.","line":229,"column":90,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[9005,9065],"text":"1 NTT Optical Fiber (Flet's Hikari\n Next - "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"‘"},"fix":{"range":[9005,9065],"text":"1 NTT Optical Fiber (Flet‘s Hikari\n Next - "},"desc":"Replace with `‘`."},{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[9005,9065],"text":"1 NTT Optical Fiber (Flet's Hikari\n Next - "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"’"},"fix":{"range":[9005,9065],"text":"1 NTT Optical Fiber (Flet’s Hikari\n Next - "},"desc":"Replace with `’`."}]}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { PageLayout } from \"@/components/layout/PageLayout\";\nimport {\n WifiIcon,\n ServerIcon,\n CurrencyYenIcon,\n ArrowLeftIcon,\n ArrowRightIcon,\n HomeIcon,\n BuildingOfficeIcon,\n} from \"@heroicons/react/24/outline\";\nimport { useInternetCatalog } from \"@/features/catalog/hooks\";\nimport { InternetPlan, InternetInstallation } from \"@/shared/types/catalog.types\";\nimport { LoadingSpinner } from \"@/components/ui/loading-spinner\";\nimport { AnimatedCard } from \"@/components/ui\";\nimport { Button } from \"@/components/ui/button\";\n\nexport function InternetPlansContainer() {\n const { data, isLoading, error } = useInternetCatalog();\n const plans = data?.plans || [];\n const installations = data?.installations || [];\n const [eligibility, setEligibility] = useState(\"\");\n\n useEffect(() => {\n if (plans.length > 0) {\n setEligibility(plans[0].offeringType || \"Home 1G\");\n }\n }, [plans]);\n\n const getEligibilityIcon = (offeringType: string) => {\n if (offeringType.toLowerCase().includes(\"home\")) return ;\n if (offeringType.toLowerCase().includes(\"apartment\"))\n return ;\n return ;\n };\n\n const getEligibilityColor = (offeringType: string) => {\n if (offeringType.toLowerCase().includes(\"home\"))\n return \"text-blue-600 bg-blue-50 border-blue-200\";\n if (offeringType.toLowerCase().includes(\"apartment\"))\n return \"text-green-600 bg-green-50 border-green-200\";\n return \"text-gray-600 bg-gray-50 border-gray-200\";\n };\n\n if (isLoading) {\n return (\n }\n >\n
\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/containers/SimConfigure.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/containers/VpnPlans.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'VpnPlan' is defined but never used.","line":6,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":6,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'VpnActivationFee' is defined but never used.","line":6,"column":19,"nodeType":null,"messageId":"unusedVar","endLine":6,"endColumn":35},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `'`, `‘`, `'`, `’`.","line":122,"column":24,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[4876,4958],"text":"\n We couldn't find any VPN plans available at this time.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"‘"},"fix":{"range":[4876,4958],"text":"\n We couldn‘t find any VPN plans available at this time.\n "},"desc":"Replace with `‘`."},{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[4876,4958],"text":"\n We couldn't find any VPN plans available at this time.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"’"},"fix":{"range":[4876,4958],"text":"\n We couldn’t find any VPN plans available at this time.\n "},"desc":"Replace with `’`."}]}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { PageLayout } from \"@/components/layout/PageLayout\";\nimport { ShieldCheckIcon, CurrencyYenIcon, ArrowLeftIcon } from \"@heroicons/react/24/outline\";\nimport { useVpnCatalog } from \"@/features/catalog/hooks\";\nimport { VpnPlan, VpnActivationFee } from \"@/shared/types/catalog.types\";\nimport { LoadingSpinner } from \"@/components/ui/loading-spinner\";\nimport { AnimatedCard } from \"@/components/ui\";\nimport { Button } from \"@/components/ui/button\";\n\nexport function VpnPlansContainer() {\n const { data, isLoading, error } = useVpnCatalog();\n const vpnPlans = data?.plans || [];\n const activationFees = data?.activationFees || [];\n\n if (isLoading) {\n return (\n }\n >\n
\n A one-time activation fee of 3000 JPY is incurred seprarately for each rental\n unit. Tax (10%) not included.\n
\n
\n )}\n
\n ) : (\n
\n \n
No VPN Plans Available
\n
\n We couldn't find any VPN plans available at this time.\n
\n \n
\n )}\n\n
\n
How It Works
\n
\n
\n SonixNet VPN is the easiest way to access video streaming services from overseas on\n your network media players such as an Apple TV, Roku, or Amazon Fire.\n
\n
\n A configured Wi-Fi router is provided for rental (no purchase required, no hidden\n fees). All you will need to do is to plug the VPN router into your existing internet\n connection.\n
\n
\n Then you can connect your network media players to the VPN Wi-Fi network, to connect\n to the VPN server.\n
\n
\n For daily Internet usage that does not require a VPN, we recommend connecting to your\n regular home Wi-Fi.\n
\n
\n
\n\n
\n
Important Disclaimer
\n
\n *1: Content subscriptions are NOT included in the SonixNet VPN package. Our VPN service\n will establish a network connection that virtually locates you in the designated server\n location, then you will sign up for the streaming services of your choice. Not all\n services/websites can be unblocked. Assist Solutions does not guarantee or bear any\n responsibility over the unblocking of any websites or the quality of the\n streaming/browsing.\n
\n
\n
\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/hooks/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/hooks/useCatalog.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'CatalogProduct' is defined but never used.","line":8,"column":15,"nodeType":null,"messageId":"unusedVar","endLine":8,"endColumn":29},{"ruleId":"@typescript-eslint/no-floating-promises","severity":2,"message":"Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator.","line":64,"column":7,"nodeType":"ExpressionStatement","messageId":"floatingVoid","endLine":64,"endColumn":63,"suggestions":[{"messageId":"floatingFixVoid","fix":{"range":[1721,1721],"text":"void "},"desc":"Add void operator to ignore."},{"messageId":"floatingFixAwait","fix":{"range":[1721,1721],"text":"await "},"desc":"Add await operator."}]},{"ruleId":"@typescript-eslint/no-floating-promises","severity":2,"message":"Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator.","line":65,"column":7,"nodeType":"ExpressionStatement","messageId":"floatingVoid","endLine":65,"endColumn":70,"suggestions":[{"messageId":"floatingFixVoid","fix":{"range":[1784,1784],"text":"void "},"desc":"Add void operator to ignore."},{"messageId":"floatingFixAwait","fix":{"range":[1784,1784],"text":"await "},"desc":"Add await operator."}]}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Catalog Hooks\n * React hooks for catalog functionality\n */\n\nimport { useQuery, useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { catalogService } from \"../services\";\nimport type { CatalogProduct, CatalogFilters, ProductConfiguration, OrderSummary } from \"../types\";\n\n/**\n * Hook to fetch all products with optional filtering\n */\nexport function useProducts(filters?: CatalogFilters) {\n return useQuery({\n queryKey: [\"catalog\", \"products\", filters],\n queryFn: () => catalogService.getProducts(filters),\n staleTime: 5 * 60 * 1000, // 5 minutes\n });\n}\n\n/**\n * Hook to fetch a specific product\n */\nexport function useProduct(id: string) {\n return useQuery({\n queryKey: [\"catalog\", \"product\", id],\n queryFn: () => catalogService.getProduct(id),\n enabled: !!id,\n staleTime: 5 * 60 * 1000, // 5 minutes\n });\n}\n\n/**\n * Hook to fetch products by category\n */\nexport function useProductsByCategory(category: \"internet\" | \"sim\" | \"vpn\") {\n return useQuery({\n queryKey: [\"catalog\", \"products\", \"category\", category],\n queryFn: () => catalogService.getProductsByCategory(category),\n staleTime: 5 * 60 * 1000, // 5 minutes\n });\n}\n\n/**\n * Hook to calculate order summary\n */\nexport function useCalculateOrder() {\n return useMutation({\n mutationFn: (configuration: ProductConfiguration) =>\n catalogService.calculateOrderSummary(configuration),\n });\n}\n\n/**\n * Hook to submit an order\n */\nexport function useSubmitOrder() {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: (orderSummary: OrderSummary) => catalogService.submitOrder(orderSummary),\n onSuccess: () => {\n // Invalidate relevant queries after successful order\n queryClient.invalidateQueries({ queryKey: [\"orders\"] });\n queryClient.invalidateQueries({ queryKey: [\"subscriptions\"] });\n },\n });\n}\n\n/**\n * Internet catalog composite hook\n * Fetches plans and installations together\n */\nexport function useInternetCatalog() {\n return useQuery({\n queryKey: [\"catalog\", \"internet\", \"all\"],\n queryFn: async () => {\n const [plans, installations, addons] = await Promise.all([\n catalogService.getInternetPlans(),\n catalogService.getInternetInstallations(),\n catalogService.getInternetAddons(),\n ]);\n return { plans, installations, addons } as const;\n },\n staleTime: 5 * 60 * 1000,\n });\n}\n\n/**\n * SIM catalog composite hook\n * Fetches plans, activation fees, and addons together\n */\nexport function useSimCatalog() {\n return useQuery({\n queryKey: [\"catalog\", \"sim\", \"all\"],\n queryFn: async () => {\n const [plans, activationFees, addons] = await Promise.all([\n catalogService.getSimPlans(),\n catalogService.getSimActivationFees(),\n catalogService.getSimAddons(),\n ]);\n return { plans, activationFees, addons } as const;\n },\n staleTime: 5 * 60 * 1000,\n });\n}\n\n/**\n * VPN catalog hook\n * Fetches VPN plans and activation fees\n */\nexport function useVpnCatalog() {\n return useQuery({\n queryKey: [\"catalog\", \"vpn\", \"all\"],\n queryFn: async () => {\n const [plans, activationFees] = await Promise.all([\n catalogService.getVpnPlans(),\n catalogService.getVpnActivationFees(),\n ]);\n return { plans, activationFees } as const;\n },\n staleTime: 5 * 60 * 1000,\n });\n}\n\n/**\n * Lookup helpers by SKU\n */\nexport function useInternetPlan(sku?: string) {\n const { data, ...rest } = useInternetCatalog();\n const plan = (data?.plans || []).find(p => p.sku === sku);\n return { plan, ...rest } as const;\n}\n\nexport function useSimPlan(sku?: string) {\n const { data, ...rest } = useSimCatalog();\n const plan = (data?.plans || []).find(p => p.sku === sku);\n return { plan, ...rest } as const;\n}\n\nexport function useVpnPlan(sku?: string) {\n const { data, ...rest } = useVpnCatalog();\n const plan = (data?.plans || []).find(p => p.sku === sku);\n return { plan, ...rest } as const;\n}\n\n/**\n * Addon/installation lookup helpers by SKU\n */\nexport function useInternetInstallation(sku?: string) {\n const { data, ...rest } = useInternetCatalog();\n const installation = (data?.installations || []).find(i => i.sku === sku);\n return { installation, ...rest } as const;\n}\n\nexport function useInternetAddon(sku?: string) {\n const { data, ...rest } = useInternetCatalog();\n const addon = (data?.addons || []).find(a => a.sku === sku);\n return { addon, ...rest } as const;\n}\n\nexport function useSimAddon(sku?: string) {\n const { data, ...rest } = useSimCatalog();\n const addon = (data?.addons || []).find(a => a.sku === sku);\n return { addon, ...rest } as const;\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/hooks/useConfigureParams.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/hooks/useInternetConfigure.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/hooks/useSimConfigure.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/services/catalog.service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/services/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/types/catalog.types.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/types/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/utils/catalog.utils.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/catalog/utils/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/checkout/containers/CheckoutContainer.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/checkout/hooks/useCheckout.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'InternetPlan' is defined but never used.","line":12,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":12,"endColumn":15},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'InternetAddon' is defined but never used.","line":13,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":13,"endColumn":16},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'InternetInstallation' is defined but never used.","line":14,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":14,"endColumn":23},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'SimPlan' is defined but never used.","line":15,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":15,"endColumn":10},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'SimAddon' is defined but never used.","line":16,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":16,"endColumn":11},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'SimActivationFee' is defined but never used.","line":17,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":17,"endColumn":19}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { useSearchParams, useRouter } from \"next/navigation\";\nimport { catalogService } from \"@/features/catalog/services/catalog.service\";\nimport { ordersService } from \"@/features/orders/services/orders.service\";\nimport { usePaymentMethods } from \"@/features/billing/hooks/useBilling\";\nimport { usePaymentRefresh } from \"@/features/billing/hooks/usePaymentRefresh\";\nimport type {\n CheckoutState,\n OrderItem,\n InternetPlan,\n InternetAddon,\n InternetInstallation,\n SimPlan,\n SimAddon,\n SimActivationFee,\n} from \"@/shared/types/catalog.types\";\nimport {\n buildInternetOrderItems,\n buildSimOrderItems,\n calculateTotals,\n buildOrderSKUs,\n} from \"@/shared/types/catalog.types\";\n\nexport interface Address {\n street: string | null;\n streetLine2: string | null;\n city: string | null;\n state: string | null;\n postalCode: string | null;\n country: string | null;\n}\n\nexport function useCheckout() {\n const params = useSearchParams();\n const router = useRouter();\n\n const [submitting, setSubmitting] = useState(false);\n const [addressConfirmed, setAddressConfirmed] = useState(false);\n const [confirmedAddress, setConfirmedAddress] = useState(null);\n\n const [checkoutState, setCheckoutState] = useState({\n loading: true,\n error: null,\n orderItems: [],\n totals: { monthlyTotal: 0, oneTimeTotal: 0 },\n });\n\n const {\n data: paymentMethods,\n isLoading: paymentMethodsLoading,\n error: paymentMethodsError,\n refetch: refetchPaymentMethods,\n } = usePaymentMethods();\n\n const paymentRefresh = usePaymentRefresh({\n refetch: refetchPaymentMethods,\n hasMethods: (data?: { totalCount?: number }) => !!data && (data.totalCount || 0) > 0,\n attachFocusListeners: true,\n });\n\n const orderType = useMemo(() => {\n const type = params.get(\"type\") || \"internet\";\n switch (type.toLowerCase()) {\n case \"sim\":\n return \"SIM\" as const;\n case \"internet\":\n return \"Internet\" as const;\n case \"vpn\":\n return \"VPN\" as const;\n default:\n return \"Other\" as const;\n }\n }, [params]);\n\n const selections = useMemo(() => {\n const obj: Record = {};\n params.forEach((v, k) => {\n if (k !== \"type\") obj[k] = v;\n });\n return obj;\n }, [params]);\n\n useEffect(() => {\n let mounted = true;\n void (async () => {\n try {\n setCheckoutState(prev => ({ ...prev, loading: true, error: null }));\n\n if (!selections.plan) {\n throw new Error(\"No plan selected. Please go back and select a plan.\");\n }\n\n let orderItems: OrderItem[] = [];\n\n if (orderType === \"Internet\") {\n const [plans, addons, installations] = await Promise.all([\n catalogService.getInternetPlans(),\n catalogService.getInternetAddons(),\n catalogService.getInternetInstallations(),\n ]);\n\n const plan = plans.find(p => p.sku === selections.plan);\n if (!plan) {\n throw new Error(\n `Internet plan not found for SKU: ${selections.plan}. Please go back and select a valid plan.`\n );\n }\n\n const addonSkus: string[] = [];\n const urlParams = new URLSearchParams(window.location.search);\n urlParams.getAll(\"addonSku\").forEach(sku => {\n if (sku && !addonSkus.includes(sku)) addonSkus.push(sku);\n });\n\n orderItems = buildInternetOrderItems(plan, addons, installations, {\n installationSku: selections.installationSku,\n addonSkus: addonSkus.length > 0 ? addonSkus : undefined,\n });\n } else if (orderType === \"SIM\") {\n const [plans, activationFees, addons] = await Promise.all([\n catalogService.getSimPlans(),\n catalogService.getSimActivationFees(),\n catalogService.getSimAddons(),\n ]);\n\n const plan = plans.find(p => p.sku === selections.plan);\n if (!plan) {\n throw new Error(\n `SIM plan not found for SKU: ${selections.plan}. Please go back and select a valid plan.`\n );\n }\n\n const addonSkus: string[] = [];\n if (selections.addonSku) addonSkus.push(selections.addonSku);\n const urlParams = new URLSearchParams(window.location.search);\n urlParams.getAll(\"addonSku\").forEach(sku => {\n if (sku && !addonSkus.includes(sku)) addonSkus.push(sku);\n });\n\n orderItems = buildSimOrderItems(plan, activationFees, addons, {\n addonSkus: addonSkus.length > 0 ? addonSkus : undefined,\n });\n }\n\n if (mounted) {\n const totals = calculateTotals(orderItems);\n setCheckoutState(prev => ({ ...prev, loading: false, orderItems, totals }));\n }\n } catch (error) {\n if (mounted) {\n setCheckoutState(prev => ({\n ...prev,\n loading: false,\n error: error instanceof Error ? error.message : \"Failed to load checkout data\",\n }));\n }\n }\n })();\n return () => {\n mounted = false;\n };\n }, [orderType, selections]);\n\n const handleSubmitOrder = useCallback(async () => {\n try {\n setSubmitting(true);\n const skus = buildOrderSKUs(checkoutState.orderItems);\n if (!skus || skus.length === 0) {\n throw new Error(\"No products selected for order. Please go back and select products.\");\n }\n\n const configurations: Record = {};\n if (selections.accessMode) configurations.accessMode = selections.accessMode;\n if (selections.simType) configurations.simType = selections.simType;\n if (selections.eid) configurations.eid = selections.eid;\n if (selections.activationType) configurations.activationType = selections.activationType;\n if (selections.scheduledAt) configurations.scheduledAt = selections.scheduledAt;\n if (selections.isMnp) configurations.isMnp = selections.isMnp;\n if (selections.reservationNumber) configurations.mnpNumber = selections.reservationNumber;\n if (selections.expiryDate) configurations.mnpExpiry = selections.expiryDate;\n if (selections.phoneNumber) configurations.mnpPhone = selections.phoneNumber;\n if (selections.mvnoAccountNumber)\n configurations.mvnoAccountNumber = selections.mvnoAccountNumber;\n if (selections.portingLastName) configurations.portingLastName = selections.portingLastName;\n if (selections.portingFirstName)\n configurations.portingFirstName = selections.portingFirstName;\n if (selections.portingLastNameKatakana)\n configurations.portingLastNameKatakana = selections.portingLastNameKatakana;\n if (selections.portingFirstNameKatakana)\n configurations.portingFirstNameKatakana = selections.portingFirstNameKatakana;\n if (selections.portingGender) configurations.portingGender = selections.portingGender;\n if (selections.portingDateOfBirth)\n configurations.portingDateOfBirth = selections.portingDateOfBirth;\n\n if (confirmedAddress) configurations.address = confirmedAddress;\n\n const orderData = {\n orderType,\n skus,\n ...(Object.keys(configurations).length > 0 && { configurations }),\n };\n\n if (orderType === \"SIM\") {\n if (!selections.eid && selections.simType === \"eSIM\") {\n throw new Error(\n \"EID is required for eSIM activation. Please go back and provide your EID.\"\n );\n }\n if (!selections.phoneNumber && !selections.mnpPhone) {\n throw new Error(\n \"Phone number is required for SIM activation. Please go back and provide a phone number.\"\n );\n }\n }\n\n const response = await ordersService.createOrder<{ sfOrderId: string }>(orderData);\n router.push(`/orders/${response.sfOrderId}?status=success`);\n } catch (error) {\n let errorMessage = \"Order submission failed\";\n if (error instanceof Error) errorMessage = error.message;\n setCheckoutState(prev => ({ ...prev, error: errorMessage }));\n } finally {\n setSubmitting(false);\n }\n }, [checkoutState.orderItems, confirmedAddress, orderType, selections, router]);\n\n const confirmAddress = useCallback((address?: Address) => {\n setAddressConfirmed(true);\n setConfirmedAddress(address || null);\n }, []);\n\n const markAddressIncomplete = useCallback(() => {\n setAddressConfirmed(false);\n setConfirmedAddress(null);\n }, []);\n\n const navigateBackToConfigure = useCallback(() => {\n const urlParams = new URLSearchParams(params.toString());\n const reviewStep = orderType === \"Internet\" ? \"4\" : \"5\";\n urlParams.set(\"step\", reviewStep);\n const configureUrl =\n orderType === \"Internet\"\n ? `/catalog/internet/configure?${urlParams.toString()}`\n : `/catalog/sim/configure?${urlParams.toString()}`;\n router.push(configureUrl);\n }, [orderType, params, router]);\n\n return {\n checkoutState,\n submitting,\n orderType,\n addressConfirmed,\n paymentMethods,\n paymentMethodsLoading,\n paymentMethodsError,\n paymentRefresh,\n confirmAddress,\n markAddressIncomplete,\n handleSubmitOrder,\n navigateBackToConfigure,\n } as const;\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/components/AccountStatusCard.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/components/ActivityFeed.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/components/DashboardActivityItem.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'id' is defined but never used.","line":38,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":38,"endColumn":5}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport {\n DocumentTextIcon,\n CheckCircleIcon,\n ServerIcon,\n ChatBubbleLeftRightIcon,\n ExclamationTriangleIcon,\n} from \"@heroicons/react/24/outline\";\nimport { format } from \"date-fns\";\nimport { cn } from \"@/lib/utils\";\nimport { getActivityIconGradient, formatActivityDate } from \"../utils/dashboard.utils\";\nimport type { Activity } from \"@customer-portal/shared\";\n\nexport interface DashboardActivityItemProps {\n id: string | number;\n type: Activity[\"type\"];\n title: string;\n description: string;\n date: string;\n onClick?: () => void;\n className?: string;\n showRelativeTime?: boolean;\n}\n\nconst ACTIVITY_ICONS: Record<\n Activity[\"type\"],\n React.ComponentType>\n> = {\n invoice_created: DocumentTextIcon,\n invoice_paid: CheckCircleIcon,\n service_activated: ServerIcon,\n case_created: ChatBubbleLeftRightIcon,\n case_closed: CheckCircleIcon,\n};\n\nexport function DashboardActivityItem({\n id,\n type,\n title,\n description,\n date,\n onClick,\n className,\n showRelativeTime = true,\n}: DashboardActivityItemProps) {\n const Icon = ACTIVITY_ICONS[type] || ExclamationTriangleIcon;\n const gradient = getActivityIconGradient(type);\n\n const formattedDate = showRelativeTime\n ? formatActivityDate(date)\n : format(new Date(date), \"MMM d, yyyy · h:mm a\");\n\n const Wrapper = onClick ? \"button\" : \"div\";\n\n return (\n \n
\n
\n \n
\n
\n\n
\n
\n {title}\n
\n\n {description &&
{description}
}\n\n
\n \n
\n
\n\n {onClick && (\n
\n \n
\n )}\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/components/QuickAction.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/components/StatCard.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/components/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/hooks/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/hooks/useDashboardSummary.ts","messages":[{"ruleId":"@typescript-eslint/only-throw-error","severity":2,"message":"Expected an error object to be thrown.","line":30,"column":15,"nodeType":"TSAsExpression","messageId":"object","endLine":33,"endColumn":28},{"ruleId":"@typescript-eslint/only-throw-error","severity":2,"message":"Expected an error object to be thrown.","line":41,"column":17,"nodeType":"TSAsExpression","messageId":"object","endLine":45,"endColumn":30},{"ruleId":"@typescript-eslint/only-throw-error","severity":2,"message":"Expected an error object to be thrown.","line":48,"column":15,"nodeType":"TSAsExpression","messageId":"object","endLine":52,"endColumn":28}],"suppressedMessages":[],"errorCount":3,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Dashboard Summary Hook\n * Provides dashboard data with proper error handling, caching, and loading states\n */\n\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { useAuthStore } from \"@/lib/auth/store\";\nimport { dashboardService } from \"../services/dashboard.service\";\nimport type { DashboardSummary, DashboardError } from \"../types/dashboard.types\";\n\n// Query key factory for dashboard queries\nexport const dashboardQueryKeys = {\n all: [\"dashboard\"] as const,\n summary: () => [...dashboardQueryKeys.all, \"summary\"] as const,\n stats: () => [...dashboardQueryKeys.all, \"stats\"] as const,\n activity: (filters?: string[]) => [...dashboardQueryKeys.all, \"activity\", filters] as const,\n nextInvoice: () => [...dashboardQueryKeys.all, \"next-invoice\"] as const,\n};\n\n/**\n * Hook for fetching dashboard summary data\n */\nexport function useDashboardSummary() {\n const { isAuthenticated, token } = useAuthStore();\n\n return useQuery({\n queryKey: dashboardQueryKeys.summary(),\n queryFn: async () => {\n if (!token) {\n throw {\n code: \"AUTHENTICATION_REQUIRED\",\n message: \"Authentication required to fetch dashboard data\",\n } as DashboardError;\n }\n\n try {\n return await dashboardService.getSummary();\n } catch (error) {\n // Transform API errors to DashboardError format\n if (error instanceof Error) {\n throw {\n code: \"FETCH_ERROR\",\n message: error.message,\n details: { originalError: error },\n } as DashboardError;\n }\n\n throw {\n code: \"UNKNOWN_ERROR\",\n message: \"An unexpected error occurred while fetching dashboard data\",\n details: { originalError: error },\n } as DashboardError;\n }\n },\n staleTime: 2 * 60 * 1000, // 2 minutes\n gcTime: 5 * 60 * 1000, // 5 minutes (formerly cacheTime)\n enabled: isAuthenticated && !!token,\n retry: (failureCount, error) => {\n // Don't retry authentication errors\n if (error?.code === \"AUTHENTICATION_REQUIRED\") {\n return false;\n }\n // Retry up to 3 times for other errors\n return failureCount < 3;\n },\n retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000), // Exponential backoff\n });\n}\n\n/**\n * Hook for refreshing dashboard data\n */\nexport function useRefreshDashboard() {\n const queryClient = useQueryClient();\n const { isAuthenticated, token } = useAuthStore();\n\n const refreshDashboard = async () => {\n if (!isAuthenticated || !token) {\n throw new Error(\"Authentication required\");\n }\n\n // Invalidate and refetch dashboard queries\n await queryClient.invalidateQueries({\n queryKey: dashboardQueryKeys.all,\n });\n\n // Optionally fetch fresh data immediately\n return queryClient.fetchQuery({\n queryKey: dashboardQueryKeys.summary(),\n queryFn: () => dashboardService.refreshSummary(),\n });\n };\n\n return { refreshDashboard };\n}\n\n/**\n * Hook for fetching dashboard stats only (lightweight)\n */\nexport function useDashboardStats() {\n const { isAuthenticated, token } = useAuthStore();\n\n return useQuery({\n queryKey: dashboardQueryKeys.stats(),\n queryFn: async () => {\n if (!token) {\n throw new Error(\"Authentication required\");\n }\n return dashboardService.getStats();\n },\n staleTime: 1 * 60 * 1000, // 1 minute\n gcTime: 3 * 60 * 1000, // 3 minutes\n enabled: isAuthenticated && !!token,\n });\n}\n\n/**\n * Hook for fetching recent activity with filtering\n */\nexport function useDashboardActivity(filters?: string[], limit = 10) {\n const { isAuthenticated, token } = useAuthStore();\n\n return useQuery({\n queryKey: dashboardQueryKeys.activity(filters),\n queryFn: async () => {\n if (!token) {\n throw new Error(\"Authentication required\");\n }\n return dashboardService.getRecentActivity(limit, filters);\n },\n staleTime: 30 * 1000, // 30 seconds\n gcTime: 2 * 60 * 1000, // 2 minutes\n enabled: isAuthenticated && !!token,\n });\n}\n\n/**\n * Hook for fetching next invoice information\n */\nexport function useNextInvoice() {\n const { isAuthenticated, token } = useAuthStore();\n\n return useQuery({\n queryKey: dashboardQueryKeys.nextInvoice(),\n queryFn: async () => {\n if (!token) {\n throw new Error(\"Authentication required\");\n }\n return dashboardService.getNextInvoice();\n },\n staleTime: 5 * 60 * 1000, // 5 minutes\n gcTime: 10 * 60 * 1000, // 10 minutes\n enabled: isAuthenticated && !!token,\n });\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/services/dashboard.service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/services/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/stores/dashboard.store.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'get' is defined but never used.","line":51,"column":11,"nodeType":null,"messageId":"unusedVar","endLine":51,"endColumn":14}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Dashboard Store\n * Local state management for dashboard UI state and preferences\n */\n\nimport { create } from \"zustand\";\nimport { persist } from \"zustand/middleware\";\nimport type { ActivityFilter } from \"../types/dashboard.types\";\n\ninterface DashboardUIState {\n // Activity filter state\n activityFilter: ActivityFilter;\n setActivityFilter: (filter: ActivityFilter) => void;\n\n // Dashboard preferences\n preferences: {\n showWelcomeMessage: boolean;\n compactView: boolean;\n autoRefresh: boolean;\n refreshInterval: number; // in seconds\n };\n updatePreferences: (preferences: Partial) => void;\n\n // UI state\n isRefreshing: boolean;\n setRefreshing: (refreshing: boolean) => void;\n\n // Error handling\n dismissedErrors: string[];\n dismissError: (errorId: string) => void;\n clearDismissedErrors: () => void;\n\n // Reset all state\n reset: () => void;\n}\n\nconst initialState = {\n activityFilter: \"all\" as ActivityFilter,\n preferences: {\n showWelcomeMessage: true,\n compactView: false,\n autoRefresh: false,\n refreshInterval: 300, // 5 minutes\n },\n isRefreshing: false,\n dismissedErrors: [],\n};\n\nexport const useDashboardStore = create()(\n persist(\n (set, get) => ({\n ...initialState,\n\n setActivityFilter: filter => {\n set({ activityFilter: filter });\n },\n\n updatePreferences: newPreferences => {\n set(state => ({\n preferences: {\n ...state.preferences,\n ...newPreferences,\n },\n }));\n },\n\n setRefreshing: refreshing => {\n set({ isRefreshing: refreshing });\n },\n\n dismissError: errorId => {\n set(state => ({\n dismissedErrors: [...state.dismissedErrors, errorId],\n }));\n },\n\n clearDismissedErrors: () => {\n set({ dismissedErrors: [] });\n },\n\n reset: () => {\n set(initialState);\n },\n }),\n {\n name: \"dashboard-store\",\n // Only persist preferences and dismissed errors\n partialize: state => ({\n preferences: state.preferences,\n dismissedErrors: state.dismissedErrors,\n }),\n }\n )\n);\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/stores/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/types/dashboard.types.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/types/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/utils/dashboard.utils.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/dashboard/utils/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/orders/containers/OrderDetail.tsx","messages":[{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `'`, `‘`, `'`, `’`.","line":191,"column":22,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[5645,5820],"text":"\n Your order has been created and submitted for processing. We will notify you as soon\n as it's approved and ready for activation.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"‘"},"fix":{"range":[5645,5820],"text":"\n Your order has been created and submitted for processing. We will notify you as soon\n as it‘s approved and ready for activation.\n "},"desc":"Replace with `‘`."},{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[5645,5820],"text":"\n Your order has been created and submitted for processing. We will notify you as soon\n as it's approved and ready for activation.\n "},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"’"},"fix":{"range":[5645,5820],"text":"\n Your order has been created and submitted for processing. We will notify you as soon\n as it’s approved and ready for activation.\n "},"desc":"Replace with `’`."}]},{"ruleId":"react/no-unescaped-entities","severity":2,"message":"`'` can be escaped with `'`, `‘`, `'`, `’`.","line":199,"column":26,"nodeType":"JSXText","messageId":"unescapedEntityAlts","suggestions":[{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[6167,6217],"text":"You'll receive an email confirmation once approved"},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"‘"},"fix":{"range":[6167,6217],"text":"You‘ll receive an email confirmation once approved"},"desc":"Replace with `‘`."},{"messageId":"replaceWithAlt","data":{"alt":"'"},"fix":{"range":[6167,6217],"text":"You'll receive an email confirmation once approved"},"desc":"Replace with `'`."},{"messageId":"replaceWithAlt","data":{"alt":"’"},"fix":{"range":[6167,6217],"text":"You’ll receive an email confirmation once approved"},"desc":"Replace with `’`."}]}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useParams, useSearchParams } from \"next/navigation\";\nimport { PageLayout } from \"@/components/layout/PageLayout\";\nimport {\n ClipboardDocumentCheckIcon,\n CheckCircleIcon,\n WifiIcon,\n DevicePhoneMobileIcon,\n LockClosedIcon,\n CubeIcon,\n} from \"@heroicons/react/24/outline\";\nimport { SubCard } from \"@/components/ui/sub-card\";\nimport { StatusPill } from \"@/components/ui/status-pill\";\nimport { ordersService } from \"@/features/orders/services/orders.service\";\n\ninterface OrderItem {\n id: string;\n quantity: number;\n unitPrice: number;\n totalPrice: number;\n product: {\n id: string;\n name: string;\n sku: string;\n whmcsProductId?: string;\n itemClass: string;\n billingCycle: string;\n };\n}\n\ninterface StatusInfo {\n label: string;\n color: string;\n bgColor: string;\n description: string;\n nextAction?: string;\n timeline?: string;\n}\n\ninterface OrderSummary {\n id: string;\n orderNumber?: string;\n status: string;\n orderType?: string;\n effectiveDate?: string;\n totalAmount?: number;\n accountName?: string;\n createdDate: string;\n lastModifiedDate: string;\n activationType?: string;\n activationStatus?: string;\n scheduledAt?: string;\n whmcsOrderId?: string;\n items?: OrderItem[];\n}\n\nconst getDetailedStatusInfo = (\n status: string,\n activationStatus?: string,\n activationType?: string,\n scheduledAt?: string\n): StatusInfo => {\n if (activationStatus === \"Activated\") {\n return {\n label: \"Service Active\",\n color: \"text-green-800\",\n bgColor: \"bg-green-50 border-green-200\",\n description: \"Your service is active and ready to use\",\n timeline: \"Service activated successfully\",\n };\n }\n if (status === \"Draft\" || status === \"Pending Review\") {\n return {\n label: \"Under Review\",\n color: \"text-blue-800\",\n bgColor: \"bg-blue-50 border-blue-200\",\n description: \"Our team is reviewing your order details\",\n nextAction: \"We will contact you within 1 business day with next steps\",\n timeline: \"Review typically takes 1 business day\",\n };\n }\n if (activationStatus === \"Scheduled\") {\n const scheduledDate = scheduledAt\n ? new Date(scheduledAt).toLocaleDateString(\"en-US\", {\n weekday: \"long\",\n month: \"long\",\n day: \"numeric\",\n })\n : \"soon\";\n return {\n label: \"Installation Scheduled\",\n color: \"text-orange-800\",\n bgColor: \"bg-orange-50 border-orange-200\",\n description: \"Your installation has been scheduled\",\n nextAction: `Installation scheduled for ${scheduledDate}`,\n timeline: \"Please be available during the scheduled time\",\n };\n }\n if (activationStatus === \"Activating\") {\n return {\n label: \"Setting Up Service\",\n color: \"text-purple-800\",\n bgColor: \"bg-purple-50 border-purple-200\",\n description: \"We're configuring your service\",\n nextAction: \"Installation team will contact you to schedule\",\n timeline: \"Setup typically takes 3-5 business days\",\n };\n }\n return {\n label: status || \"Processing\",\n color: \"text-gray-800\",\n bgColor: \"bg-gray-50 border-gray-200\",\n description: \"Your order is being processed\",\n timeline: \"We will update you as progress is made\",\n };\n};\n\nconst getOrderTypeIcon = (orderType?: string) => {\n switch (orderType) {\n case \"Internet\":\n return ;\n case \"SIM\":\n return ;\n case \"VPN\":\n return ;\n default:\n return ;\n }\n};\n\nconst calculateDetailedTotals = (items: OrderItem[]) => {\n let monthlyTotal = 0;\n let oneTimeTotal = 0;\n items.forEach(item => {\n if (item.product.billingCycle === \"Monthly\") monthlyTotal += item.totalPrice || 0;\n else oneTimeTotal += item.totalPrice || 0;\n });\n return { monthlyTotal, oneTimeTotal };\n};\n\nexport function OrderDetailContainer() {\n const params = useParams<{ id: string }>();\n const searchParams = useSearchParams();\n const [data, setData] = useState(null);\n const [error, setError] = useState(null);\n const isNewOrder = searchParams.get(\"status\") === \"success\";\n\n useEffect(() => {\n let mounted = true;\n const fetchStatus = async () => {\n try {\n const order = await ordersService.getOrderById(params.id);\n if (mounted) setData(order || null);\n } catch (e) {\n if (mounted) setError(e instanceof Error ? e.message : \"Failed to load order\");\n }\n };\n void fetchStatus();\n const interval = setInterval(() => {\n void fetchStatus();\n }, 5000);\n return () => {\n mounted = false;\n clearInterval(interval);\n };\n }, [params.id]);\n\n return (\n }\n title={data ? `${data.orderType} Service Order` : \"Order Details\"}\n description={\n data\n ? `Order #${data.orderNumber || String(data.id).slice(-8)}`\n : \"Loading order details...\"\n }\n >\n {error &&
{error}
}\n {isNewOrder && (\n
\n
\n \n
\n
\n Order Submitted Successfully!\n
\n
\n Your order has been created and submitted for processing. We will notify you as soon\n as it's approved and ready for activation.\n
\n
\n
\n What happens next:\n
\n
\n
Our team will review your order (within 1 business day)
\n
You'll receive an email confirmation once approved
\n
We will schedule activation based on your preferences
\n
This page will update automatically as your order progresses
\n \n Data usage is updated in real-time and may take a few minutes to reflect recent\n activity\n
\n
\n \n Top-up data will be available immediately after successful processing\n
\n
\n \n SIM cancellation is permanent and cannot be undone\n
\n {simInfo.details.simType === \"esim\" && (\n
\n \n eSIM profile reissue will provide a new QR code for activation\n
\n )}\n
\n
\n\n {/* (On desktop, details+usage are above; on mobile they appear first since this section is above actions) */}\n
\n
\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/sim-management/components/TopUpModal.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/sim-management/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/subscriptions/components/SubscriptionActions.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'isInternetService' is assigned a value but never used.","line":74,"column":9,"nodeType":null,"messageId":"unusedVar","endLine":74,"endColumn":26},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'isVpnService' is assigned a value but never used.","line":78,"column":9,"nodeType":null,"messageId":"unusedVar","endLine":78,"endColumn":21},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":193,"column":25,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":193,"endColumn":40},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":204,"column":25,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":204,"endColumn":39},{"ruleId":"@typescript-eslint/no-misused-promises","severity":2,"message":"Promise-returning function provided to attribute where a void return was expected.","line":269,"column":29,"nodeType":"JSXExpressionContainer","messageId":"voidReturnAttribute","endLine":269,"endColumn":43}],"suppressedMessages":[],"errorCount":3,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useState } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport {\n PauseIcon,\n PlayIcon,\n XMarkIcon,\n ArrowUpIcon,\n ArrowDownIcon,\n DocumentTextIcon,\n CreditCardIcon,\n Cog6ToothIcon,\n ExclamationTriangleIcon,\n} from \"@heroicons/react/24/outline\";\nimport { Button } from \"@/components/ui/button\";\nimport { SubCard } from \"@/components/ui/sub-card\";\nimport type { Subscription } from \"@customer-portal/shared\";\nimport { cn } from \"@/lib/utils\";\nimport { useSubscriptionAction } from \"../hooks\";\n\ninterface SubscriptionActionsProps {\n subscription: Subscription;\n onActionSuccess?: () => void;\n className?: string;\n}\n\ninterface ActionButtonProps {\n icon: React.ReactNode;\n label: string;\n description: string;\n variant?: \"default\" | \"destructive\" | \"outline\" | \"secondary\";\n disabled?: boolean;\n onClick: () => void;\n}\n\nconst ActionButton = ({\n icon,\n label,\n description,\n variant = \"outline\",\n disabled,\n onClick,\n}: ActionButtonProps) => (\n
\n }\n label=\"Upgrade Plan\"\n description=\"Upgrade to a higher tier plan with more features\"\n onClick={handleUpgrade}\n />\n\n }\n label=\"Downgrade Plan\"\n description=\"Switch to a lower tier plan\"\n onClick={handleDowngrade}\n />\n
\n
\n )}\n\n {/* Billing Actions */}\n
\n
Billing & Payment
\n
\n }\n label=\"View Invoices\"\n description=\"View billing history and download invoices\"\n onClick={handleViewInvoices}\n />\n\n }\n label=\"Manage Payment\"\n description=\"Update payment methods and billing information\"\n onClick={handleManagePayment}\n />\n
\n {isCancelled\n ? \"This subscription has been cancelled and is no longer active. No further actions are available.\"\n : \"This subscription is pending activation. Actions will be available once the subscription is active.\"}\n
\n
\n )}\n
\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/subscriptions/components/SubscriptionCard.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Link' is defined but never used.","line":4,"column":8,"nodeType":null,"messageId":"unusedVar","endLine":4,"endColumn":12},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ServerIcon' is defined but never used.","line":7,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":7,"endColumn":13}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { forwardRef } from \"react\";\nimport Link from \"next/link\";\nimport { format } from \"date-fns\";\nimport {\n ServerIcon,\n CheckCircleIcon,\n ExclamationTriangleIcon,\n ClockIcon,\n XCircleIcon,\n CalendarIcon,\n ArrowTopRightOnSquareIcon,\n} from \"@heroicons/react/24/outline\";\nimport { StatusPill } from \"@/components/ui/status-pill\";\nimport { Button } from \"@/components/ui/button\";\nimport { SubCard } from \"@/components/ui/sub-card\";\nimport { formatCurrency, getCurrencyLocale } from \"@/utils/currency\";\nimport type { Subscription } from \"@customer-portal/shared\";\nimport { cn } from \"@/lib/utils\";\n\ninterface SubscriptionCardProps {\n subscription: Subscription;\n variant?: \"list\" | \"grid\";\n showActions?: boolean;\n onViewClick?: (subscription: Subscription) => void;\n className?: string;\n}\n\nconst getStatusIcon = (status: string) => {\n switch (status) {\n case \"Active\":\n return ;\n case \"Suspended\":\n return ;\n case \"Pending\":\n return ;\n case \"Cancelled\":\n case \"Terminated\":\n return ;\n default:\n return ;\n }\n};\n\nconst getStatusVariant = (status: string) => {\n switch (status) {\n case \"Active\":\n return \"success\" as const;\n case \"Suspended\":\n return \"warning\" as const;\n case \"Pending\":\n return \"info\" as const;\n case \"Cancelled\":\n case \"Terminated\":\n return \"neutral\" as const;\n default:\n return \"neutral\" as const;\n }\n};\n\nconst formatDate = (dateString: string | undefined) => {\n if (!dateString) return \"N/A\";\n try {\n return format(new Date(dateString), \"MMM d, yyyy\");\n } catch {\n return \"Invalid date\";\n }\n};\n\nconst getBillingCycleLabel = (cycle: string) => {\n const name = cycle.toLowerCase();\n const looksLikeActivation = name.includes(\"activation\") || name.includes(\"setup\");\n return looksLikeActivation ? \"One-time\" : cycle;\n};\n\nexport const SubscriptionCard = forwardRef(\n ({ subscription, variant = \"list\", showActions = true, onViewClick, className }, ref) => {\n const handleViewClick = () => {\n if (onViewClick) {\n onViewClick(subscription);\n }\n };\n\n if (variant === \"grid\") {\n return (\n \n
\n \n );\n }\n);\n\nSubscriptionCard.displayName = \"SubscriptionCard\";\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/subscriptions/components/SubscriptionDetails.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/subscriptions/components/SubscriptionStatusBadge.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/subscriptions/components/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/subscriptions/containers/SimCancel.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":51,"column":69,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":51,"endColumn":72,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1981,1984],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1981,1984],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/require-await","severity":2,"message":"Async arrow function 'fetchEmail' has no 'await' expression.","line":61,"column":33,"nodeType":"ArrowFunctionExpression","messageId":"missingAwait","endLine":61,"endColumn":35,"suggestions":[{"messageId":"removeAsync","fix":{"range":[2264,2270],"text":""},"desc":"Remove 'async'."}]}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport Link from \"next/link\";\nimport { useParams, useRouter } from \"next/navigation\";\nimport { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { simActionsService } from \"@/features/subscriptions/services/sim-actions.service\";\nimport { useAuthStore } from \"@/lib/auth/store\";\nimport type { SimDetails } from \"@/features/sim-management/components/SimDetailsCard\";\n\ntype Step = 1 | 2 | 3;\n\nfunction Notice({ title, children }: { title: string; children: ReactNode }) {\n return (\n
\n Cancel SIM: Permanently cancel your SIM service. This action cannot be undone and will\n terminate your service immediately.\n
\n\n {step === 1 && (\n
\n
\n \n \n
\n \n \n
\n Cancellation takes effect at the start of the selected month.\n
\n
\n
\n
\n \n
\n
\n )}\n\n {step === 2 && (\n
\n
\n \n Online cancellations must be made from this website by the 25th of the desired\n cancellation month. Once a request of a cancellation of the SONIXNET SIM is accepted\n from this online form, a confirmation email containing details of the SIM plan will\n be sent to the registered email address. The SIM card is a rental piece of hardware\n and must be returned to Assist Solutions upon cancellation. The cancellation request\n through this website retains to your SIM subscriptions only. To cancel any other\n services with Assist Solutions (home internet etc.) please contact Assist Solutions\n at info@asolutions.co.jp\n \n \n The SONIXNET SIM has a minimum contract term agreement of three months (sign-up\n month is not included in the minimum term of three months; ie. sign-up in January =\n minimum term is February, March, April). If the minimum contract term is not\n fulfilled, the monthly fees of the remaining months will be charged upon\n cancellation.\n \n \n Cancellation of option services only (Voice Mail, Call Waiting) while keeping the\n base plan active is not possible from this online form. Please contact Assist\n Solutions Customer Support (info@asolutions.co.jp) for more information. Upon\n cancelling the base plan, all additional options associated with the requested SIM\n plan will be cancelled.\n \n \n Upon cancellation the SIM phone number will be lost. In order to keep the phone\n number active to be used with a different cellular provider, a request for an MNP\n transfer (administrative fee \\\\1,000yen+tax) is necessary. The MNP cannot be\n requested from this online form. Please contact Assist Solutions Customer Support\n (info@asolutions.co.jp) for more information.\n \n
\n Your registered email address is:{\" \"}\n {registeredEmail}\n
\n )}\n
\n You will receive a cancellation confirmation email. If you would like to receive this\n email on a different address, please enter the address below.\n
\n Your cancellation request is not confirmed yet. This is the final page. To finalize\n your cancellation request please proceed from REQUEST CANCELLATION below.\n
\n
\n \n \n
\n
\n )}\n
\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/home/tnarantuya/projects/new-portal-website/apps/portal/src/features/subscriptions/containers/SubscriptionDetail.tsx","messages":[{"ruleId":"prettier/prettier","severity":1,"message":"Replace `\"use·client\"` with `(\"use·client\")`","line":2,"column":1,"nodeType":null,"messageId":"replace","endLine":2,"endColumn":13,"fix":{"range":[66,78],"text":"(\"use client\")"}},{"ruleId":"@typescript-eslint/no-unused-expressions","severity":1,"message":"Expected an assignment or function call and instead saw an expression.","line":2,"column":1,"nodeType":"ExpressionStatement","messageId":"unusedExpression","endLine":2,"endColumn":14},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ArrowTopRightOnSquareIcon' is defined but never used.","line":19,"column":3,"nodeType":null,"messageId":"unusedVar","endLine":19,"endColumn":28},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'currentPage' is assigned a value but never used.","line":30,"column":10,"nodeType":null,"messageId":"unusedVar","endLine":30,"endColumn":21},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'setCurrentPage' is assigned a value but never used.","line":30,"column":23,"nodeType":null,"messageId":"unusedVar","endLine":30,"endColumn":37},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'itemsPerPage' is assigned a value but never used.","line":31,"column":9,"nodeType":null,"messageId":"unusedVar","endLine":31,"endColumn":21},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'getStatusColor' is assigned a value but never used.","line":77,"column":9,"nodeType":null,"messageId":"unusedVar","endLine":77,"endColumn":23},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'getInvoiceStatusIcon' is assigned a value but never used.","line":94,"column":9,"nodeType":null,"messageId":"unusedVar","endLine":94,"endColumn":29},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'getInvoiceStatusColor' is assigned a value but never used.","line":107,"column":9,"nodeType":null,"messageId":"unusedVar","endLine":107,"endColumn":30},{"ruleId":"prettier/prettier","severity":1,"message":"Insert `··`","line":220,"column":19,"nodeType":null,"messageId":"insert","endLine":220,"endColumn":19,"fix":{"range":[7468,7468],"text":" "}},{"ruleId":"prettier/prettier","severity":1,"message":"Insert `··`","line":221,"column":1,"nodeType":null,"messageId":"insert","endLine":221,"endColumn":1,"fix":{"range":[7480,7480],"text":" "}},{"ruleId":"prettier/prettier","severity":1,"message":"Insert `····`","line":222,"column":19,"nodeType":null,"messageId":"insert","endLine":222,"endColumn":19,"fix":{"range":[7576,7576],"text":" "}},{"ruleId":"prettier/prettier","severity":1,"message":"Insert `····`","line":223,"column":1,"nodeType":null,"messageId":"insert","endLine":223,"endColumn":1,"fix":{"range":[7588,7588],"text":" "}}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":13,"fixableErrorCount":0,"fixableWarningCount":5,"source":"import { LoadingSpinner } from \"@/components/ui/loading-spinner\";\n\"use client\";\n\nimport { SubCard } from \"@/components/ui/sub-card\";\nimport { DetailHeader } from \"@/components/common/DetailHeader\";\n\nimport { useEffect, useState } from \"react\";\nimport { useParams, useSearchParams } from \"next/navigation\";\nimport Link from \"next/link\";\nimport {\n ArrowLeftIcon,\n ServerIcon,\n CheckCircleIcon,\n ExclamationTriangleIcon,\n ClockIcon,\n XCircleIcon,\n CalendarIcon,\n DocumentTextIcon,\n ArrowTopRightOnSquareIcon,\n} from \"@heroicons/react/24/outline\";\nimport { format } from \"date-fns\";\nimport { useSubscription } from \"@/features/subscriptions/hooks\";\nimport { InvoicesList } from \"@/features/billing/components/InvoiceList/InvoiceList\";\nimport { formatCurrency as sharedFormatCurrency, getCurrencyLocale } from \"@/utils/currency\";\nimport { SimManagementSection } from \"@/features/sim-management\";\n\nexport function SubscriptionDetailContainer() {\n const params = useParams();\n const searchParams = useSearchParams();\n const [currentPage, setCurrentPage] = useState(1);\n const itemsPerPage = 10;\n const [showInvoices, setShowInvoices] = useState(true);\n const [showSimManagement, setShowSimManagement] = useState(false);\n\n const subscriptionId = parseInt(params.id as string);\n const { data: subscription, isLoading, error } = useSubscription(subscriptionId);\n // Invoices are now rendered via shared InvoiceList\n\n useEffect(() => {\n const updateVisibility = () => {\n const hash = typeof window !== \"undefined\" ? window.location.hash : \"\";\n const service = (searchParams.get(\"service\") || \"\").toLowerCase();\n const isSimContext = hash.includes(\"sim-management\") || service === \"sim\";\n if (isSimContext) {\n setShowInvoices(false);\n setShowSimManagement(true);\n } else {\n setShowInvoices(true);\n setShowSimManagement(false);\n }\n };\n updateVisibility();\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"hashchange\", updateVisibility);\n return () => window.removeEventListener(\"hashchange\", updateVisibility);\n }\n return;\n }, [searchParams]);\n\n const getStatusIcon = (status: string) => {\n switch (status) {\n case \"Active\":\n return ;\n case \"Suspended\":\n return ;\n case \"Terminated\":\n return ;\n case \"Cancelled\":\n return ;\n case \"Pending\":\n return ;\n default:\n return ;\n }\n };\n\n const getStatusColor = (status: string) => {\n switch (status) {\n case \"Active\":\n return \"bg-green-100 text-green-800\";\n case \"Suspended\":\n return \"bg-yellow-100 text-yellow-800\";\n case \"Terminated\":\n return \"bg-red-100 text-red-800\";\n case \"Cancelled\":\n return \"bg-gray-100 text-gray-800\";\n case \"Pending\":\n return \"bg-blue-100 text-blue-800\";\n default:\n return \"bg-gray-100 text-gray-800\";\n }\n };\n\n const getInvoiceStatusIcon = (status: string) => {\n switch (status) {\n case \"Paid\":\n return ;\n case \"Overdue\":\n return ;\n case \"Unpaid\":\n return ;\n default:\n return ;\n }\n };\n\n const getInvoiceStatusColor = (status: string) => {\n switch (status) {\n case \"Paid\":\n return \"bg-green-100 text-green-800\";\n case \"Overdue\":\n return \"bg-red-100 text-red-800\";\n case \"Unpaid\":\n return \"bg-yellow-100 text-yellow-800\";\n case \"Cancelled\":\n return \"bg-gray-100 text-gray-800\";\n default:\n return \"bg-gray-100 text-gray-800\";\n }\n };\n\n const formatDate = (dateString: string | undefined) => {\n if (!dateString) return \"N/A\";\n try {\n return format(new Date(dateString), \"MMM d, yyyy\");\n } catch {\n return \"Invalid date\";\n }\n };\n\n const formatCurrency = (amount: number) =>\n sharedFormatCurrency(amount || 0, { currency: \"JPY\", locale: getCurrencyLocale(\"JPY\") });\n\n const formatBillingLabel = (cycle: string) => {\n switch (cycle) {\n case \"Monthly\":\n return \"Monthly Billing\";\n case \"Annually\":\n return \"Annual Billing\";\n case \"Quarterly\":\n return \"Quarterly Billing\";\n case \"Semi-Annually\":\n return \"Semi-Annual Billing\";\n case \"Biennially\":\n return \"Biennial Billing\";\n case \"Triennially\":\n return \"Triennial Billing\";\n default:\n return \"One-time Payment\";\n }\n };\n\n if (isLoading) {\n return (\n
\n
\n \n
Loading subscription...
\n
\n
\n );\n }\n\n if (error || !subscription) {\n return (\n
\n
\n
\n
\n \n
\n
\n
Error loading subscription
\n
\n {error instanceof Error ? error.message : \"Subscription not found\"}\n