- Add billing-usage feature module with types, API clients, hooks, and components - Create PlanCard, PlanSelector, SubscriptionStatusBadge, InvoiceList, UsageSummaryCard, CouponInput components - Add BillingPage, PlansPage, InvoicesPage, UsagePage - Update routes to include billing section Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
156 lines
4.0 KiB
TypeScript
156 lines
4.0 KiB
TypeScript
import { useState, useCallback, useEffect } from 'react';
|
|
import { plansApi } from '../api';
|
|
import type { SubscriptionPlan, PlanFilters, CreatePlanDto, UpdatePlanDto } from '../types';
|
|
|
|
interface UsePlansState {
|
|
plans: SubscriptionPlan[];
|
|
total: number;
|
|
page: number;
|
|
totalPages: number;
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
}
|
|
|
|
interface UsePlansReturn extends UsePlansState {
|
|
filters: PlanFilters;
|
|
setFilters: (filters: PlanFilters) => void;
|
|
refresh: () => Promise<void>;
|
|
createPlan: (data: CreatePlanDto) => Promise<SubscriptionPlan>;
|
|
updatePlan: (id: string, data: UpdatePlanDto) => Promise<SubscriptionPlan>;
|
|
deletePlan: (id: string) => Promise<void>;
|
|
activatePlan: (id: string) => Promise<void>;
|
|
deactivatePlan: (id: string) => Promise<void>;
|
|
}
|
|
|
|
export function usePlans(initialFilters?: PlanFilters): UsePlansReturn {
|
|
const [state, setState] = useState<UsePlansState>({
|
|
plans: [],
|
|
total: 0,
|
|
page: 1,
|
|
totalPages: 1,
|
|
isLoading: true,
|
|
error: null,
|
|
});
|
|
|
|
const [filters, setFilters] = useState<PlanFilters>(initialFilters || { page: 1, limit: 10 });
|
|
|
|
const fetchPlans = useCallback(async () => {
|
|
setState((prev) => ({ ...prev, isLoading: true, error: null }));
|
|
try {
|
|
const response = await plansApi.getAll(filters);
|
|
setState({
|
|
plans: response.data,
|
|
total: response.meta.total,
|
|
page: response.meta.page,
|
|
totalPages: response.meta.totalPages,
|
|
isLoading: false,
|
|
error: null,
|
|
});
|
|
} catch (err) {
|
|
setState((prev) => ({
|
|
...prev,
|
|
isLoading: false,
|
|
error: err instanceof Error ? err.message : 'Error al cargar planes',
|
|
}));
|
|
}
|
|
}, [filters]);
|
|
|
|
useEffect(() => {
|
|
fetchPlans();
|
|
}, [fetchPlans]);
|
|
|
|
const createPlan = async (data: CreatePlanDto): Promise<SubscriptionPlan> => {
|
|
const plan = await plansApi.create(data);
|
|
await fetchPlans();
|
|
return plan;
|
|
};
|
|
|
|
const updatePlan = async (id: string, data: UpdatePlanDto): Promise<SubscriptionPlan> => {
|
|
const plan = await plansApi.update(id, data);
|
|
await fetchPlans();
|
|
return plan;
|
|
};
|
|
|
|
const deletePlan = async (id: string): Promise<void> => {
|
|
await plansApi.delete(id);
|
|
await fetchPlans();
|
|
};
|
|
|
|
const activatePlan = async (id: string): Promise<void> => {
|
|
await plansApi.activate(id);
|
|
await fetchPlans();
|
|
};
|
|
|
|
const deactivatePlan = async (id: string): Promise<void> => {
|
|
await plansApi.deactivate(id);
|
|
await fetchPlans();
|
|
};
|
|
|
|
return {
|
|
...state,
|
|
filters,
|
|
setFilters,
|
|
refresh: fetchPlans,
|
|
createPlan,
|
|
updatePlan,
|
|
deletePlan,
|
|
activatePlan,
|
|
deactivatePlan,
|
|
};
|
|
}
|
|
|
|
export function usePlan(id: string | undefined) {
|
|
const [plan, setPlan] = useState<SubscriptionPlan | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchPlan = useCallback(async () => {
|
|
if (!id) {
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const data = await plansApi.getById(id);
|
|
setPlan(data);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar plan');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [id]);
|
|
|
|
useEffect(() => {
|
|
fetchPlan();
|
|
}, [fetchPlan]);
|
|
|
|
return { plan, isLoading, error, refresh: fetchPlan };
|
|
}
|
|
|
|
export function usePublicPlans() {
|
|
const [plans, setPlans] = useState<SubscriptionPlan[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const fetchPlans = useCallback(async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
try {
|
|
const data = await plansApi.getPublic();
|
|
setPlans(data);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Error al cargar planes');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchPlans();
|
|
}, [fetchPlans]);
|
|
|
|
return { plans, isLoading, error, refresh: fetchPlans };
|
|
}
|