Created
December 28, 2025 15:44
-
-
Save ch1nux/54d5a76bb791e575e79afe7bf81fea6d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| "use client"; | |
| import { useState } from "react"; | |
| import { useRouter } from "next/navigation"; | |
| import Image from "next/image"; | |
| import { | |
| Alert, | |
| Avatar, | |
| Button, | |
| Card, | |
| CardContent, | |
| CircularProgress, | |
| IconButton, | |
| Typography, | |
| } from "@mui/material"; | |
| import { ArrowBack, DateRange } from "@mui/icons-material"; | |
| import { cn } from "@/lib/utils"; | |
| import WowAddAccountStep1 from "./WowAddAccountStep1"; | |
| import WowDomiciliacionConfirmation from "./WowDomiciliacionConfirmation"; | |
| import WowBillSelectionStep from "./WowBillSelectionStep"; | |
| import WowPaymentSummaryStep from "./WowPaymentSummaryStep"; | |
| import WowPaymentConfirmationStep from "./WowPaymentConfirmationStep"; | |
| import SuccessScreen from "@/components/payments/SuccessScreen"; | |
| import NotificationScreen from "@/components/ui/NotificationScreen"; | |
| import { WowAccountFormData } from "@/lib/validation/servicesSchemas"; | |
| import { WowBill, RegisterWowInternetResponse, WowService, WowInvoice } from "@/types/services"; | |
| import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; | |
| import { useUserStore } from "@/stores/useUserStore"; | |
| import getUserServicesQueryOptions from "@/queryOptions/services/getUserServicesQueryOptions"; | |
| import getWowInternetDebtQueryOptions from "@/queryOptions/services/getWowInternetDebtQueryOptions"; | |
| import registerWowInternetServiceMutationOptions from "@/mutationOptions/services/registerWowInternetServiceMutationOptions"; | |
| import payWowServiceMutationOptions from "@/mutationOptions/services/payWowServiceMutationOptions"; | |
| import getWalletBalanceQueryOptions from "@/queryOptions/wallet/getWalletBalanceQueryOptions"; | |
| // Helper function to format date | |
| const formatDate = (dateString: string) => { | |
| const date = new Date(dateString); | |
| return date.toLocaleDateString("es-VE", { | |
| day: "numeric", | |
| month: "short", | |
| year: "numeric", | |
| }); | |
| }; | |
| // Helper function to generate initials from service alias | |
| const getInitialsFromAlias = (alias: string) => { | |
| if (!alias || alias.trim() === "") return "WW"; | |
| const firstTwo = alias.substring(0, 2).toUpperCase(); | |
| return firstTwo.length >= 2 ? firstTwo : "WW"; | |
| }; | |
| type Step = | |
| | "list" | |
| | "add-account" | |
| | "account-info" | |
| | "confirmation" | |
| | "bill-selection" | |
| | "payment-summary" | |
| | "payment-confirmation" | |
| | "success"; | |
| type FlowType = "domiciliar" | "one-time"; | |
| interface WowAccountsViewProps { | |
| businessId: string; | |
| } | |
| export default function WowAccountsView({ businessId }: WowAccountsViewProps) { | |
| const router = useRouter(); | |
| const queryClient = useQueryClient(); | |
| const userId = useUserStore((state) => state.userId); | |
| const walletId = useUserStore((state) => state.walletId); | |
| const { | |
| data: services, | |
| isLoading, | |
| error, | |
| } = useQuery(getUserServicesQueryOptions(userId ?? "", businessId)); | |
| const { mutateAsync: registerService, isPending: isRegistering } = useMutation( | |
| registerWowInternetServiceMutationOptions() | |
| ); | |
| const { mutateAsync: payService, isPending: isPaying } = useMutation( | |
| payWowServiceMutationOptions() | |
| ); | |
| // Fetch wallet balance for payment validation | |
| const { data: walletData } = useQuery( | |
| getWalletBalanceQueryOptions(userId ?? "") | |
| ); | |
| const activeWallet = walletId | |
| ? walletData?.wallets?.find((wallet) => wallet.id === walletId) | |
| : walletData?.wallets?.[0]; | |
| const [currentStep, setCurrentStep] = useState<Step>("list"); | |
| const [selectedService, setSelectedService] = useState<WowService | null>(null); | |
| // Fetch debt when clicking on existing service | |
| const { data: serviceDebt, isLoading: isLoadingDebt } = useQuery( | |
| getWowInternetDebtQueryOptions(selectedService?.id ?? "") | |
| ); | |
| const [flowType, setFlowType] = useState<FlowType>("domiciliar"); | |
| const [accountData, setAccountData] = useState<WowAccountFormData | null>( | |
| null | |
| ); | |
| const [registrationData, setRegistrationData] = useState<RegisterWowInternetResponse | null>( | |
| null | |
| ); | |
| const [selectedInvoices, setSelectedInvoices] = useState<WowInvoice[]>([]); | |
| const [customAmount, setCustomAmount] = useState<number | null>(null); | |
| const [errorMessage, setErrorMessage] = useState<string | null>(null); | |
| const handleAddNewAccount = () => { | |
| setCurrentStep("add-account"); | |
| setErrorMessage(null); | |
| }; | |
| const handleBackToList = () => { | |
| setCurrentStep("list"); | |
| setAccountData(null); | |
| setSelectedService(null); | |
| setRegistrationData(null); | |
| setErrorMessage(null); | |
| }; | |
| const handleServiceClick = (service: WowService) => { | |
| setSelectedService(service); | |
| setFlowType(service.is_direct_debit ? "domiciliar" : "one-time"); | |
| setCurrentStep("account-info"); | |
| }; | |
| const handleAccountInfoBack = () => { | |
| setCurrentStep("list"); | |
| setSelectedService(null); | |
| setErrorMessage(null); | |
| }; | |
| const handleDomiciliarFromAccountInfo = () => { | |
| // For existing accounts, domiciliar means setting up direct debit | |
| // This will need backend integration to update the service | |
| // For now, just show bill selection | |
| setFlowType("domiciliar"); | |
| setCurrentStep("bill-selection"); | |
| }; | |
| const handlePagarUnaVezFromAccountInfo = () => { | |
| setFlowType("one-time"); | |
| setCurrentStep("bill-selection"); | |
| }; | |
| const handleDomiciliar = async (data: WowAccountFormData) => { | |
| try { | |
| setErrorMessage(null); | |
| const response = await registerService({ | |
| username: data.usuario, | |
| email: data.email, | |
| alias: data.alias ?? "", | |
| isDirectDebitActive: true, | |
| }); | |
| await queryClient.invalidateQueries({ queryKey: ["User-Services", userId, businessId] }); | |
| setAccountData(data); | |
| setRegistrationData(response); | |
| setFlowType("domiciliar"); | |
| setCurrentStep("confirmation"); | |
| } catch (error) { | |
| console.error("Failed to register service:", error); | |
| setErrorMessage("No se pudo registrar el servicio. Por favor, intenta nuevamente."); | |
| } | |
| }; | |
| const handlePagarUnaVez = async (data: WowAccountFormData) => { | |
| try { | |
| setErrorMessage(null); | |
| const response = await registerService({ | |
| username: data.usuario, | |
| email: data.email, | |
| alias: data.alias ?? "", | |
| isDirectDebitActive: false, | |
| }); | |
| await queryClient.invalidateQueries({ queryKey: ["User-Services", userId, businessId] }); | |
| setAccountData(data); | |
| setRegistrationData(response); | |
| setFlowType("one-time"); | |
| setCurrentStep("bill-selection"); | |
| } catch (error) { | |
| console.error("Failed to register service:", error); | |
| setErrorMessage("No se pudo registrar el servicio. Por favor, intenta nuevamente."); | |
| } | |
| }; | |
| const handleBillSelectionContinue = (invoices: WowInvoice[]) => { | |
| setSelectedInvoices(invoices); | |
| setCurrentStep("payment-summary"); | |
| setErrorMessage(null); | |
| }; | |
| const handleBillSelectionBack = () => { | |
| setCurrentStep("add-account"); | |
| setErrorMessage(null); | |
| }; | |
| const handlePaymentSummaryContinue = (useCustomAmount: boolean, amount?: number) => { | |
| if (useCustomAmount && amount) { | |
| setCustomAmount(amount); | |
| } else { | |
| setCustomAmount(null); | |
| } | |
| setCurrentStep("payment-confirmation"); | |
| setErrorMessage(null); | |
| }; | |
| const handlePaymentSummaryBack = () => { | |
| setCurrentStep("bill-selection"); | |
| setErrorMessage(null); | |
| }; | |
| const handlePaymentConfirm = async () => { | |
| const serviceId = selectedService?.id ?? registrationData?.id; | |
| if (!userId || !serviceId) return; | |
| const totalAmount = selectedInvoices.reduce((sum, invoice) => sum + invoice.dueAmount, 0); | |
| const paymentAmount = customAmount ?? totalAmount; | |
| try { | |
| setErrorMessage(null); | |
| await payService({ | |
| userId, | |
| serviceId, | |
| body: { | |
| walletId: activeWallet?.id ?? 0, | |
| amount: paymentAmount, | |
| description: `Pago WOW Internet - ${selectedInvoices.map(inv => inv.number).join(", ")}`, | |
| invoiceNumbers: selectedInvoices.map(inv => inv.number), | |
| }, | |
| }); | |
| await queryClient.invalidateQueries({ queryKey: ["Wallet-Balance", userId] }); | |
| setCurrentStep("success"); | |
| } catch (error) { | |
| console.error("Failed to pay service:", error); | |
| setErrorMessage("No se pudo procesar el pago. Por favor, intenta nuevamente."); | |
| } | |
| }; | |
| const handlePaymentCancel = () => { | |
| setCurrentStep("payment-summary"); | |
| setErrorMessage(null); | |
| }; | |
| const handleConfirmDomiciliacion = () => { | |
| setCurrentStep("success"); | |
| setErrorMessage(null); | |
| }; | |
| const handleCancelConfirmation = () => { | |
| setCurrentStep("add-account"); | |
| setErrorMessage(null); | |
| }; | |
| const handleBackFromConfirmation = () => { | |
| setCurrentStep("add-account"); | |
| setErrorMessage(null); | |
| }; | |
| const handleGoToProductSummary = () => { | |
| // Navigate to dashboard | |
| router.push("/dashboard"); | |
| }; | |
| // Render Account Info (when clicking existing account) | |
| if (currentStep === "account-info" && selectedService) { | |
| return ( | |
| <Card variant="outlined" className={cn("min-w-[600px] mx-auto mt-8")}> | |
| <CardContent className="p-4"> | |
| <div className={cn("flex flex-col gap-[30px] w-full")}> | |
| {/* Header with back button, logo, and service name */} | |
| <div className={cn("flex items-center gap-3")}> | |
| <IconButton | |
| onClick={handleAccountInfoBack} | |
| size="small" | |
| className="p-2" | |
| > | |
| <ArrowBack /> | |
| </IconButton> | |
| <div className="relative w-10 h-10 flex-shrink-0"> | |
| <Image | |
| src="/services/wow-internet.png" | |
| alt="WOW Internet" | |
| width={40} | |
| height={40} | |
| className="rounded-full object-cover" | |
| /> | |
| </div> | |
| <Typography variant="h5" className="font-bold flex-1"> | |
| WOW Internet | |
| </Typography> | |
| </div> | |
| {/* Section Title */} | |
| <Typography variant="h6" className="font-medium"> | |
| Información de cuenta | |
| </Typography> | |
| {/* Account Card */} | |
| <div className="border border-gray-200 rounded p-4 flex items-center gap-4"> | |
| <Avatar className="bg-gray-400 text-white"> | |
| {getInitialsFromAlias(selectedService.alias)} | |
| </Avatar> | |
| <div className="flex-1 min-w-0"> | |
| <Typography variant="subtitle1" className="font-normal"> | |
| {selectedService.alias || "Sin alias"} | |
| </Typography> | |
| <Typography variant="body2" className="text-gray-500"> | |
| Último pago - {formatDate(selectedService.last_payment_date)} | |
| </Typography> | |
| </div> | |
| </div> | |
| {/* Action Buttons */} | |
| <div className="flex flex-col gap-4"> | |
| <Button | |
| variant="contained" | |
| fullWidth | |
| onClick={handleDomiciliarFromAccountInfo} | |
| className={cn( | |
| "bg-primary hover:bg-primary/90 text-white font-medium", | |
| "normal-case text-[15px] py-2" | |
| )} | |
| > | |
| DOMICILIAR PAGO | |
| </Button> | |
| <Button | |
| variant="outlined" | |
| fullWidth | |
| onClick={handlePagarUnaVezFromAccountInfo} | |
| className={cn( | |
| "border-primary text-primary font-medium", | |
| "normal-case text-[15px] py-2" | |
| )} | |
| > | |
| PAGAR UNA VEZ | |
| </Button> | |
| {/* TODO: Uncomment when payment history feature is implemented | |
| <Button | |
| variant="outlined" | |
| fullWidth | |
| className={cn( | |
| "border-primary text-primary font-medium", | |
| "normal-case text-[15px] py-2" | |
| )} | |
| > | |
| VER PAGOS REALIZADOS | |
| </Button> | |
| */} | |
| </div> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
| // Render Step 1: Add Account Form | |
| if (currentStep === "add-account") { | |
| return ( | |
| <Card variant="outlined" className={cn("min-w-[600px] mx-auto mt-8")}> | |
| <CardContent className="p-4"> | |
| <WowAddAccountStep1 | |
| onBack={handleBackToList} | |
| onDomiciliar={handleDomiciliar} | |
| onPagarUnaVez={handlePagarUnaVez} | |
| isLoading={isRegistering} | |
| errorMessage={errorMessage || undefined} | |
| /> | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
| // Render Step 2: Bill Selection (for one-time flow or existing service) | |
| if (currentStep === "bill-selection") { | |
| // Use invoices from registration (new service) or from debt query (existing service) | |
| const invoices = selectedService ? serviceDebt ?? [] : registrationData?.invoices ?? []; | |
| const isLoadingInvoices = selectedService ? isLoadingDebt : false; | |
| return ( | |
| <Card variant="outlined" className={cn("min-w-[600px] mx-auto mt-8")}> | |
| <CardContent className="p-4"> | |
| <div className={cn("flex flex-col gap-[30px] w-full")}> | |
| {/* Header with back button, logo, and service name */} | |
| <div className={cn("flex items-center gap-3")}> | |
| <IconButton | |
| onClick={handleBillSelectionBack} | |
| size="small" | |
| className="p-2" | |
| > | |
| <ArrowBack /> | |
| </IconButton> | |
| <div className="relative w-10 h-10 flex-shrink-0"> | |
| <Image | |
| src="/services/wow-internet.png" | |
| alt="WOW Internet" | |
| width={40} | |
| height={40} | |
| className="rounded-full object-cover" | |
| /> | |
| </div> | |
| <Typography variant="h5" className="font-bold flex-1"> | |
| WOW Internet | |
| </Typography> | |
| </div> | |
| {isLoadingInvoices ? ( | |
| <div className="flex justify-center items-center py-8"> | |
| <CircularProgress /> | |
| </div> | |
| ) : ( | |
| <WowBillSelectionStep | |
| invoices={invoices} | |
| onContinue={handleBillSelectionContinue} | |
| onBack={handleBillSelectionBack} | |
| /> | |
| )} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
| // Render Step 3: Payment Summary (for one-time flow) | |
| if (currentStep === "payment-summary") { | |
| return ( | |
| <Card variant="outlined" className={cn("min-w-[600px] mx-auto mt-8")}> | |
| <CardContent className="p-4"> | |
| <div className={cn("flex flex-col gap-[30px] w-full")}> | |
| {/* Header with back button, logo, and service name */} | |
| <div className={cn("flex items-center gap-3")}> | |
| <IconButton | |
| onClick={handlePaymentSummaryBack} | |
| size="small" | |
| className="p-2" | |
| > | |
| <ArrowBack /> | |
| </IconButton> | |
| <div className="relative w-10 h-10 flex-shrink-0"> | |
| <Image | |
| src="/services/wow-internet.png" | |
| alt="WOW Internet" | |
| width={40} | |
| height={40} | |
| className="rounded-full object-cover" | |
| /> | |
| </div> | |
| <Typography variant="h5" className="font-bold flex-1"> | |
| WOW Internet | |
| </Typography> | |
| </div> | |
| <WowPaymentSummaryStep | |
| selectedInvoices={selectedInvoices} | |
| onContinue={handlePaymentSummaryContinue} | |
| onBack={handlePaymentSummaryBack} | |
| /> | |
| </div> | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
| // Render Step 4: Payment Confirmation (for one-time flow) | |
| if (currentStep === "payment-confirmation") { | |
| return ( | |
| <Card variant="outlined" className={cn("min-w-[600px] mx-auto mt-8")}> | |
| <CardContent className="p-4"> | |
| <WowPaymentConfirmationStep | |
| selectedInvoices={selectedInvoices} | |
| customAmount={customAmount} | |
| onConfirm={handlePaymentConfirm} | |
| onCancel={handlePaymentCancel} | |
| isLoading={isPaying} | |
| walletBalance={activeWallet?.balance ?? 0} | |
| walletAccountNumber={activeWallet?.id?.toString() ?? ""} | |
| /> | |
| {errorMessage && ( | |
| <Alert severity="error" className="mt-4"> | |
| {errorMessage} | |
| </Alert> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
| // Render Domiciliación Confirmation (only for domiciliar flow) | |
| if (currentStep === "confirmation" && registrationData) { | |
| return ( | |
| <Card variant="outlined" className={cn("min-w-[600px] mx-auto mt-8")}> | |
| <CardContent className="p-4"> | |
| <WowDomiciliacionConfirmation | |
| invoices={registrationData.invoices ?? []} | |
| onBack={handleBackFromConfirmation} | |
| onContinue={handleConfirmDomiciliacion} | |
| onCancel={handleCancelConfirmation} | |
| /> | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
| // Render Step 5: Success Screen | |
| if (currentStep === "success") { | |
| const totalAmount = selectedInvoices.reduce((sum, invoice) => sum + invoice.dueAmount, 0); | |
| return ( | |
| <Card variant="outlined" className={cn("min-w-[600px] mx-auto mt-8")}> | |
| <CardContent className="p-4"> | |
| <SuccessScreen | |
| title={ | |
| flowType === "domiciliar" | |
| ? "Domiciliación realizada correctamente" | |
| : "Pago de Servicio Exitoso" | |
| } | |
| subtitle={ | |
| flowType === "domiciliar" | |
| ? "Has configurado tu pago recurrente con éxito. Este cargo se realizará automáticamente cada día 16 de cada mes. Te notificaremos cada vez que procesemos el pago" | |
| : `Tu pago por el servicio WOW Internet se ha procesado correctamente por un monto de Bs ${totalAmount.toLocaleString("es-VE", { minimumFractionDigits: 2 })}. ¡Disfruta de tu conexión sin interrupciones!` | |
| } | |
| buttonText="IR AL DASHBOARD" | |
| onGoToMain={handleGoToProductSummary} | |
| /> | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
| // Default: Render Account List | |
| return ( | |
| <Card variant="outlined" className={cn("min-w-[600px] mx-auto mt-8")}> | |
| <CardContent className="p-4"> | |
| <div className={cn("flex flex-col gap-[30px] w-full")}> | |
| {/* Header with back button, logo, and service name */} | |
| <div className={cn("flex items-center gap-3")}> | |
| <IconButton onClick={() => router.back()} size="small" className="p-2"> | |
| <ArrowBack /> | |
| </IconButton> | |
| <div className="relative w-10 h-10 flex-shrink-0"> | |
| <Image | |
| src="/services/wow-internet.png" | |
| alt="WOW Internet" | |
| width={40} | |
| height={40} | |
| className="rounded-full object-cover" | |
| /> | |
| </div> | |
| <Typography variant="h5" className="font-bold flex-1"> | |
| WOW Internet | |
| </Typography> | |
| </div> | |
| {/* Section Title */} | |
| <Typography variant="h6" className="font-medium"> | |
| Seleccionar Cuenta | |
| </Typography> | |
| {/* Add New Account Button */} | |
| <Button | |
| variant="contained" | |
| fullWidth | |
| onClick={handleAddNewAccount} | |
| className={cn( | |
| "bg-primary hover:bg-primary/90 text-white font-medium", | |
| "normal-case text-[15px] py-2" | |
| )} | |
| > | |
| AGREGAR NUEVA CUENTA | |
| </Button> | |
| {/* Loading State */} | |
| {isLoading && ( | |
| <div className="flex justify-center items-center py-8"> | |
| <CircularProgress /> | |
| </div> | |
| )} | |
| {/* Error State */} | |
| {error && ( | |
| <Typography color="error" className="text-center py-4"> | |
| Error al cargar los servicios. Por favor, intenta nuevamente. | |
| </Typography> | |
| )} | |
| {/* Account List */} | |
| {!isLoading && !error && services && ( | |
| <div className="flex flex-col gap-4"> | |
| {services.length === 0 ? ( | |
| <NotificationScreen | |
| type="empty" | |
| down={ | |
| <Typography variant="h5" className="font-bold text-center"> | |
| No tienes cuentas<br />Registradas | |
| </Typography> | |
| } | |
| /> | |
| ) : ( | |
| services.map((service: WowService) => ( | |
| <div | |
| key={service.id} | |
| onClick={() => handleServiceClick(service)} | |
| className={cn( | |
| "border border-gray-200 rounded p-4 flex items-center gap-4", | |
| "hover:bg-gray-50 cursor-pointer transition-colors" | |
| )} | |
| > | |
| <Avatar className="bg-gray-400 text-white"> | |
| {getInitialsFromAlias(service.alias)} | |
| </Avatar> | |
| <div className="flex-1 min-w-0"> | |
| <Typography variant="subtitle1" className="font-normal"> | |
| {service.alias || "Sin alias"} | |
| </Typography> | |
| <Typography variant="body2" className="text-gray-500"> | |
| {service.is_direct_debit | |
| ? `Domiciliado a Fecha de Corte - ${formatDate(service.next_payment_date)}` | |
| : `Último pago - ${formatDate(service.last_payment_date)}` | |
| } | |
| </Typography> | |
| </div> | |
| {service.is_direct_debit && ( | |
| <IconButton | |
| edge="end" | |
| aria-label="calendario" | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| // TODO: Open calendar dialog/modal | |
| }} | |
| > | |
| <DateRange /> | |
| </IconButton> | |
| )} | |
| </div> | |
| )) | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| </CardContent> | |
| </Card> | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment