"use client"; import { useCallback, useMemo, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import type { SubscriptionTierSummary } from "@/lib/prisma/queries/subscription-tiers"; type FeedbackState = | { type: "success"; message: string } | { type: "error"; message: string } | null; interface AdminSubscriptionDashboardProps { initialTiers: SubscriptionTierSummary[]; } interface TierFormState { name: string; slug: string; description: string; level: number; priceMonthly: string; priceYearly: string; isActive: boolean; features: string; } function mapTierToFormState(tier: SubscriptionTierSummary): TierFormState { return { name: tier.name, slug: tier.slug, description: tier.description ?? "", level: tier.level, priceMonthly: tier.price_monthly ? tier.price_monthly.toString() : "", priceYearly: tier.price_yearly ? tier.price_yearly.toString() : "", isActive: tier.is_active, features: (tier.features ?? []) .map( (feature) => `${feature.feature_key}|${feature.label}|${feature.description ?? ""}`, ) .join("\n"), }; } function parseFeatures(text: string) { if (!text.trim()) { return []; } return text .split("\n") .map((line) => line.trim()) .filter(Boolean) .map((line, index) => { const [featureKey, label, description] = line .split("|") .map((part) => part?.trim() ?? ""); return { feature_key: featureKey, label: label || featureKey, description: description || null, sort_order: index, }; }); } export function AdminSubscriptionDashboard({ initialTiers, }: AdminSubscriptionDashboardProps) { const [tiers, setTiers] = useState(initialTiers); const [isLoading, setIsLoading] = useState(false); const [feedback, setFeedback] = useState(null); const [createForm, setCreateForm] = useState({ name: "", slug: "", description: "", level: 0, priceMonthly: "", priceYearly: "", isActive: true, features: "", }); const [editForms, setEditForms] = useState>( () => Object.fromEntries( initialTiers.map((tier) => [ tier.subscription_tier_id, mapTierToFormState(tier), ]), ), ); const [assignUserId, setAssignUserId] = useState(""); const [assignTierId, setAssignTierId] = useState("free"); const resetFeedback = () => setFeedback(null); const refreshTiers = useCallback(async () => { const response = await fetch("/api/subscription-tiers", { method: "GET", credentials: "include", cache: "no-store", }); if (!response.ok) { throw new Error("Erro ao atualizar lista de planos."); } const data = await response.json(); setTiers(data.tiers); setEditForms( Object.fromEntries( data.tiers.map((tier: SubscriptionTierSummary) => [ tier.subscription_tier_id, mapTierToFormState(tier), ]), ), ); }, []); const handleCreateTier = async () => { resetFeedback(); setIsLoading(true); try { const payload = { name: createForm.name, slug: createForm.slug, description: createForm.description || null, level: Number.parseInt(createForm.level.toString(), 10), priceMonthly: createForm.priceMonthly ? Number.parseFloat(createForm.priceMonthly) : null, priceYearly: createForm.priceYearly ? Number.parseFloat(createForm.priceYearly) : null, isActive: createForm.isActive, features: parseFeatures(createForm.features), }; const response = await fetch("/api/subscription-tiers", { method: "POST", headers: { "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify(payload), }); if (!response.ok) { const data = await response.json().catch(() => ({})); throw new Error(data.error || "Erro ao criar plano."); } setCreateForm({ name: "", slug: "", description: "", level: 0, priceMonthly: "", priceYearly: "", isActive: true, features: "", }); await refreshTiers(); setFeedback({ type: "success", message: "Plano criado com sucesso." }); } catch (error) { setFeedback({ type: "error", message: error instanceof Error ? error.message : "Não foi possível criar o plano.", }); } finally { setIsLoading(false); } }; const handleUpdateTier = async (tierId: number) => { resetFeedback(); setIsLoading(true); try { const form = editForms[tierId]; const payload = { name: form.name, slug: form.slug, description: form.description || null, level: Number.parseInt(form.level.toString(), 10), priceMonthly: form.priceMonthly ? Number.parseFloat(form.priceMonthly) : null, priceYearly: form.priceYearly ? Number.parseFloat(form.priceYearly) : null, isActive: form.isActive, features: parseFeatures(form.features), }; const response = await fetch(`/api/subscription-tiers/${tierId}`, { method: "PATCH", headers: { "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify(payload), }); if (!response.ok) { const data = await response.json().catch(() => ({})); throw new Error(data.error || "Erro ao atualizar plano."); } await refreshTiers(); setFeedback({ type: "success", message: "Plano atualizado com sucesso.", }); } catch (error) { setFeedback({ type: "error", message: error instanceof Error ? error.message : "Não foi possível atualizar o plano.", }); } finally { setIsLoading(false); } }; const handleDeleteTier = async (tierId: number, slug: string) => { resetFeedback(); if (!confirm(`Deseja realmente remover o plano "${slug}"?`)) { return; } setIsLoading(true); try { const response = await fetch(`/api/subscription-tiers/${tierId}`, { method: "DELETE", credentials: "include", }); if (!response.ok) { const data = await response.json().catch(() => ({})); throw new Error(data.error || "Erro ao remover plano."); } await refreshTiers(); setFeedback({ type: "success", message: "Plano removido com sucesso." }); } catch (error) { setFeedback({ type: "error", message: error instanceof Error ? error.message : "Não foi possível remover o plano.", }); } finally { setIsLoading(false); } }; const handleAssignTier = async () => { resetFeedback(); if (!assignUserId.trim()) { setFeedback({ type: "error", message: "Informe um ID de usuário válido.", }); return; } setIsLoading(true); try { const payload = { userId: Number.parseInt(assignUserId, 10), tierSlug: assignTierId, }; const response = await fetch("/api/subscription-tiers/assign", { method: "POST", headers: { "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify(payload), }); if (!response.ok) { const data = await response.json().catch(() => ({})); throw new Error(data.error || "Erro ao atualizar plano do usuário."); } setAssignUserId(""); setFeedback({ type: "success", message: "Plano do usuário atualizado." }); } catch (error) { setFeedback({ type: "error", message: error instanceof Error ? error.message : "Não foi possível atualizar o plano do usuário.", }); } finally { setIsLoading(false); } }; const orderedTiers = useMemo( () => [...tiers].sort((a, b) => { if (a.level === b.level) { return a.subscription_tier_id - b.subscription_tier_id; } return a.level - b.level; }), [tiers], ); return (
{feedback && (
{feedback.message}
)} Adicionar novo plano
setCreateForm((prev) => ({ ...prev, name: event.target.value, })) } />
setCreateForm((prev) => ({ ...prev, slug: event.target.value, })) } />
setCreateForm((prev) => ({ ...prev, level: Number.parseInt(event.target.value || "0", 10), })) } />
setCreateForm((prev) => ({ ...prev, priceMonthly: event.target.value, })) } />
setCreateForm((prev) => ({ ...prev, priceYearly: event.target.value, })) } />