Skip to content

Instantly share code, notes, and snippets.

@ch1nux
Created December 28, 2025 15:44
Show Gist options
  • Select an option

  • Save ch1nux/54d5a76bb791e575e79afe7bf81fea6d to your computer and use it in GitHub Desktop.

Select an option

Save ch1nux/54d5a76bb791e575e79afe7bf81fea6d to your computer and use it in GitHub Desktop.
"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