- Added authentication checks in useInvoices, useInvoice, and usePaymentMethods hooks to ensure data fetching only occurs for authenticated users. - Updated usePaymentRefresh to prevent refresh actions when the user is not authenticated. - Refactored AddressConfirmation component to improve button layout and accessibility. - Enhanced InternetPlanCard to format plan names for clearer presentation. - Streamlined InternetConfigureContainer and related components to utilize Zustand for state management, improving code clarity and maintainability. - Updated SimConfigureView to simplify step transitions and improve user experience.
216 lines
5.8 KiB
TypeScript
216 lines
5.8 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
useMutation,
|
|
useQuery,
|
|
type UseMutationOptions,
|
|
type UseMutationResult,
|
|
type UseQueryOptions,
|
|
type UseQueryResult,
|
|
} from "@tanstack/react-query";
|
|
|
|
// ✅ Generic utilities from lib
|
|
import {
|
|
apiClient,
|
|
queryKeys,
|
|
getDataOrDefault,
|
|
getDataOrThrow,
|
|
type QueryParams,
|
|
} from "@/lib/api";
|
|
|
|
// ✅ Single consolidated import from domain
|
|
import {
|
|
// Types
|
|
type Invoice,
|
|
type InvoiceList,
|
|
type InvoiceSsoLink,
|
|
type InvoiceQueryParams,
|
|
type InvoiceStatus,
|
|
// Schemas
|
|
invoiceSchema,
|
|
invoiceListSchema,
|
|
// Constants
|
|
INVOICE_STATUS,
|
|
} from "@customer-portal/domain/billing";
|
|
|
|
import { type PaymentMethodList } from "@customer-portal/domain/payments";
|
|
import { useAuthSession } from "@/features/auth/services/auth.store";
|
|
|
|
// Constants
|
|
const EMPTY_INVOICE_LIST: InvoiceList = {
|
|
invoices: [],
|
|
pagination: {
|
|
page: 1,
|
|
totalItems: 0,
|
|
totalPages: 0,
|
|
},
|
|
};
|
|
|
|
const EMPTY_PAYMENT_METHODS: PaymentMethodList = {
|
|
paymentMethods: [],
|
|
totalCount: 0,
|
|
};
|
|
|
|
const FALLBACK_STATUS: InvoiceStatus = INVOICE_STATUS.DRAFT;
|
|
|
|
function ensureInvoiceStatus(invoice: Invoice): Invoice {
|
|
return {
|
|
...invoice,
|
|
status: (invoice.status as InvoiceStatus | undefined) ?? FALLBACK_STATUS,
|
|
};
|
|
}
|
|
|
|
function normalizeInvoiceList(list: InvoiceList): InvoiceList {
|
|
return {
|
|
...list,
|
|
invoices: list.invoices.map(ensureInvoiceStatus),
|
|
pagination: {
|
|
page: list.pagination?.page ?? 1,
|
|
totalItems: list.pagination?.totalItems ?? 0,
|
|
totalPages: list.pagination?.totalPages ?? 0,
|
|
nextCursor: list.pagination?.nextCursor,
|
|
},
|
|
};
|
|
}
|
|
|
|
// Type helpers for React Query
|
|
type InvoicesQueryKey = ReturnType<typeof queryKeys.billing.invoices>;
|
|
type InvoiceQueryKey = ReturnType<typeof queryKeys.billing.invoice>;
|
|
type PaymentMethodsQueryKey = ReturnType<typeof queryKeys.billing.paymentMethods>;
|
|
|
|
type InvoicesQueryOptions = Omit<
|
|
UseQueryOptions<InvoiceList, Error, InvoiceList, InvoicesQueryKey>,
|
|
"queryKey" | "queryFn"
|
|
>;
|
|
|
|
type InvoiceQueryOptions = Omit<
|
|
UseQueryOptions<Invoice, Error, Invoice, InvoiceQueryKey>,
|
|
"queryKey" | "queryFn"
|
|
>;
|
|
|
|
type PaymentMethodsQueryOptions = Omit<
|
|
UseQueryOptions<PaymentMethodList, Error, PaymentMethodList, PaymentMethodsQueryKey>,
|
|
"queryKey" | "queryFn"
|
|
>;
|
|
|
|
type SsoLinkMutationOptions = UseMutationOptions<
|
|
InvoiceSsoLink,
|
|
Error,
|
|
{ invoiceId: number; target?: "view" | "download" | "pay" }
|
|
>;
|
|
|
|
// Helper functions
|
|
function toQueryParams(params: InvoiceQueryParams): QueryParams {
|
|
const query: QueryParams = {};
|
|
for (const [key, value] of Object.entries(params)) {
|
|
if (value === undefined) {
|
|
continue;
|
|
}
|
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
query[key] = value;
|
|
}
|
|
}
|
|
return query;
|
|
}
|
|
|
|
// API functions
|
|
async function fetchInvoices(params?: InvoiceQueryParams): Promise<InvoiceList> {
|
|
const query = params ? toQueryParams(params) : undefined;
|
|
const response = await apiClient.GET<InvoiceList>(
|
|
"/api/invoices",
|
|
query ? { params: { query } } : undefined
|
|
);
|
|
const data = getDataOrDefault(response, EMPTY_INVOICE_LIST);
|
|
const parsed = invoiceListSchema.parse(data);
|
|
return normalizeInvoiceList(parsed);
|
|
}
|
|
|
|
async function fetchInvoice(id: string): Promise<Invoice> {
|
|
const response = await apiClient.GET<Invoice>("/api/invoices/{id}", {
|
|
params: { path: { id } },
|
|
});
|
|
const invoice = getDataOrThrow(response, "Invoice not found");
|
|
const parsed = invoiceSchema.parse(invoice);
|
|
return ensureInvoiceStatus(parsed);
|
|
}
|
|
|
|
async function fetchPaymentMethods(): Promise<PaymentMethodList> {
|
|
const response = await apiClient.GET<PaymentMethodList>("/api/invoices/payment-methods");
|
|
return getDataOrDefault(response, EMPTY_PAYMENT_METHODS);
|
|
}
|
|
|
|
// Exported hooks
|
|
export function useInvoices(
|
|
params?: InvoiceQueryParams,
|
|
options?: InvoicesQueryOptions
|
|
): UseQueryResult<InvoiceList, Error> {
|
|
const { isAuthenticated } = useAuthSession();
|
|
const queryKeyParams = params ? { ...params } : undefined;
|
|
return useQuery({
|
|
queryKey: queryKeys.billing.invoices(queryKeyParams),
|
|
queryFn: () => fetchInvoices(params),
|
|
enabled: isAuthenticated,
|
|
...options,
|
|
});
|
|
}
|
|
|
|
export function useInvoice(
|
|
id: string,
|
|
options?: InvoiceQueryOptions
|
|
): UseQueryResult<Invoice, Error> {
|
|
const { isAuthenticated } = useAuthSession();
|
|
return useQuery({
|
|
queryKey: queryKeys.billing.invoice(id),
|
|
queryFn: () => fetchInvoice(id),
|
|
enabled: isAuthenticated && Boolean(id),
|
|
...options,
|
|
});
|
|
}
|
|
|
|
export function usePaymentMethods(
|
|
options?: PaymentMethodsQueryOptions
|
|
): UseQueryResult<PaymentMethodList, Error> {
|
|
const { isAuthenticated } = useAuthSession();
|
|
return useQuery({
|
|
queryKey: queryKeys.billing.paymentMethods(),
|
|
queryFn: fetchPaymentMethods,
|
|
enabled: isAuthenticated,
|
|
...options,
|
|
});
|
|
}
|
|
|
|
export function useCreateInvoiceSsoLink(
|
|
options?: SsoLinkMutationOptions
|
|
): UseMutationResult<
|
|
InvoiceSsoLink,
|
|
Error,
|
|
{ invoiceId: number; target?: "view" | "download" | "pay" }
|
|
> {
|
|
return useMutation({
|
|
mutationFn: async ({ invoiceId, target }) => {
|
|
const response = await apiClient.POST<InvoiceSsoLink>("/api/invoices/{id}/sso-link", {
|
|
params: {
|
|
path: { id: invoiceId },
|
|
query: target ? { target } : undefined,
|
|
},
|
|
});
|
|
return getDataOrThrow(response, "Failed to create SSO link");
|
|
},
|
|
...options,
|
|
});
|
|
}
|
|
|
|
export function useCreatePaymentMethodsSsoLink(
|
|
options?: UseMutationOptions<InvoiceSsoLink, Error, void>
|
|
): UseMutationResult<InvoiceSsoLink, Error, void> {
|
|
return useMutation({
|
|
mutationFn: async () => {
|
|
const response = await apiClient.POST<InvoiceSsoLink>("/auth/sso-link", {
|
|
body: { destination: "index.php?rp=/account/paymentmethods" },
|
|
});
|
|
return getDataOrThrow(response, "Failed to create payment methods SSO link");
|
|
},
|
|
...options,
|
|
});
|
|
}
|