diff --git a/src/components/admin/AnalyticsTab.tsx b/src/components/admin/AnalyticsTab.tsx deleted file mode 100644 index 2effd8d..0000000 --- a/src/components/admin/AnalyticsTab.tsx +++ /dev/null @@ -1,367 +0,0 @@ -import { useState, useEffect, useMemo } from "react"; -import { supabase } from "@/integrations/supabase/client"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Badge } from "@/components/ui/badge"; -import { - BarChart, - Bar, - XAxis, - YAxis, - CartesianGrid, - Tooltip, - ResponsiveContainer, - PieChart, - Pie, - Cell, - LineChart, - Line, - Legend, -} from "recharts"; -import { - TrendingUp, - TrendingDown, - DollarSign, - ShoppingCart, - Users, - CreditCard, - Calendar, - RefreshCw, -} from "lucide-react"; -import { CONFIG } from "@/config/payments"; - -interface Order { - id: string; - amount: number; - status: string; - payment_method: string; - created_at: string; - customer_email: string; -} - -const COLORS = ["hsl(142, 76%, 36%)", "hsl(38, 92%, 50%)", "hsl(0, 84%, 60%)", "hsl(240, 4%, 46%)"]; - -const AnalyticsTab = () => { - const [orders, setOrders] = useState([]); - const [loading, setLoading] = useState(true); - const [timeRange, setTimeRange] = useState("30"); - - useEffect(() => { - fetchOrders(); - }, [timeRange]); - - const fetchOrders = async () => { - setLoading(true); - try { - const startDate = new Date(); - startDate.setDate(startDate.getDate() - parseInt(timeRange)); - - const { data, error } = await supabase - .from("orders") - .select("id, amount, status, payment_method, created_at, customer_email") - .gte("created_at", startDate.toISOString()) - .order("created_at", { ascending: true }); - - if (error) throw error; - setOrders(data || []); - } catch (error) { - console.error("Failed to fetch analytics data:", error); - } finally { - setLoading(false); - } - }; - - const stats = useMemo(() => { - const verifiedOrders = orders.filter(o => o.status === "verified" || o.status === "completed"); - const totalRevenue = verifiedOrders.reduce((sum, o) => sum + Number(o.amount), 0); - const avgOrderValue = verifiedOrders.length > 0 ? totalRevenue / verifiedOrders.length : 0; - const uniqueCustomers = new Set(orders.map(o => o.customer_email)).size; - const conversionRate = orders.length > 0 ? (verifiedOrders.length / orders.length) * 100 : 0; - - return { - totalRevenue, - totalOrders: orders.length, - verifiedOrders: verifiedOrders.length, - avgOrderValue, - uniqueCustomers, - conversionRate, - }; - }, [orders]); - - const revenueByDay = useMemo(() => { - const grouped: Record = {}; - - orders.forEach(order => { - if (order.status === "verified" || order.status === "completed") { - const date = new Date(order.created_at).toLocaleDateString("en-US", { - month: "short", - day: "numeric", - }); - if (!grouped[date]) { - grouped[date] = { date, revenue: 0, orders: 0 }; - } - grouped[date].revenue += Number(order.amount); - grouped[date].orders += 1; - } - }); - - return Object.values(grouped); - }, [orders]); - - const paymentMethodData = useMemo(() => { - const grouped: Record = {}; - - orders.forEach(order => { - const method = order.payment_method || "unknown"; - grouped[method] = (grouped[method] || 0) + 1; - }); - - return Object.entries(grouped).map(([name, value]) => ({ - name: name.charAt(0).toUpperCase() + name.slice(1), - value, - })); - }, [orders]); - - const statusData = useMemo(() => { - const grouped: Record = {}; - - orders.forEach(order => { - grouped[order.status] = (grouped[order.status] || 0) + 1; - }); - - return Object.entries(grouped).map(([name, value]) => ({ - name: name.charAt(0).toUpperCase() + name.slice(1), - value, - })); - }, [orders]); - - if (loading) { - return ( -
- - Loading analytics... -
- ); - } - - return ( -
- {/* Time Range Selector */} -
-

- - Analytics Overview -

- -
- - {/* Key Metrics */} -
- - - - Total Revenue - - - - -
- {CONFIG.store.currencySymbol}{stats.totalRevenue.toFixed(2)} -
-

- From {stats.verifiedOrders} verified orders -

-
-
- - - - - Total Orders - - - - -
{stats.totalOrders}
-

- {stats.verifiedOrders} completed -

-
-
- - - - - Avg. Order Value - - - - -
- {CONFIG.store.currencySymbol}{stats.avgOrderValue.toFixed(2)} -
-

- Per verified order -

-
-
- - - - - Conversion Rate - - - - -
{stats.conversionRate.toFixed(1)}%
-

- {stats.uniqueCustomers} unique customers -

-
-
-
- - {/* Charts */} -
- {/* Revenue Over Time */} - - - Revenue Over Time - - - {revenueByDay.length === 0 ? ( -
- No revenue data for this period -
- ) : ( - - - - - `$${value}`} - /> - [`$${value.toFixed(2)}`, "Revenue"]} - /> - - - - )} -
-
- - {/* Orders by Status */} - - - Orders by Status - - - {statusData.length === 0 ? ( -
- No order data for this period -
- ) : ( - - - `${name}: ${value}`} - > - {statusData.map((_, index) => ( - - ))} - - - - - )} -
-
- - {/* Payment Methods */} - - - Orders by Payment Method - - - {paymentMethodData.length === 0 ? ( -
- No payment data for this period -
- ) : ( - - - - - - [value, "Orders"]} - /> - - - - )} -
-
-
-
- ); -}; - -export default AnalyticsTab; diff --git a/src/components/admin/DiscountCodesTab.tsx b/src/components/admin/DiscountCodesTab.tsx deleted file mode 100644 index 9969e89..0000000 --- a/src/components/admin/DiscountCodesTab.tsx +++ /dev/null @@ -1,456 +0,0 @@ -import { useState, useEffect } from "react"; -import { supabase } from "@/integrations/supabase/client"; -import { useToast } from "@/hooks/use-toast"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Switch } from "@/components/ui/switch"; -import { Badge } from "@/components/ui/badge"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Plus, Pencil, Trash2, Ticket, RefreshCw, Copy } from "lucide-react"; - -interface DiscountCode { - id: string; - code: string; - description: string | null; - discount_type: string; - discount_value: number; - min_order_amount: number; - max_uses: number | null; - current_uses: number; - valid_from: string; - valid_until: string | null; - is_active: boolean; - created_at: string; -} - -const DiscountCodesTab = () => { - const { toast } = useToast(); - const [codes, setCodes] = useState([]); - const [loading, setLoading] = useState(true); - const [dialogOpen, setDialogOpen] = useState(false); - const [editingCode, setEditingCode] = useState(null); - const [saving, setSaving] = useState(false); - - const [formData, setFormData] = useState({ - code: "", - description: "", - discount_type: "percentage", - discount_value: "", - min_order_amount: "0", - max_uses: "", - valid_until: "", - is_active: true, - }); - - useEffect(() => { - fetchCodes(); - }, []); - - const fetchCodes = async () => { - setLoading(true); - try { - const { data, error } = await supabase - .from("discount_codes") - .select("*") - .order("created_at", { ascending: false }); - - if (error) throw error; - setCodes(data || []); - } catch (error) { - console.error("Failed to fetch discount codes:", error); - toast({ - title: "Error", - description: "Failed to load discount codes.", - variant: "destructive", - }); - } finally { - setLoading(false); - } - }; - - const resetForm = () => { - setFormData({ - code: "", - description: "", - discount_type: "percentage", - discount_value: "", - min_order_amount: "0", - max_uses: "", - valid_until: "", - is_active: true, - }); - setEditingCode(null); - }; - - const generateCode = () => { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - let code = ""; - for (let i = 0; i < 8; i++) { - code += chars.charAt(Math.floor(Math.random() * chars.length)); - } - setFormData({ ...formData, code }); - }; - - const openEditDialog = (code: DiscountCode) => { - setEditingCode(code); - setFormData({ - code: code.code, - description: code.description || "", - discount_type: code.discount_type, - discount_value: code.discount_value.toString(), - min_order_amount: code.min_order_amount.toString(), - max_uses: code.max_uses?.toString() || "", - valid_until: code.valid_until ? code.valid_until.split("T")[0] : "", - is_active: code.is_active, - }); - setDialogOpen(true); - }; - - const handleSubmit = async () => { - if (!formData.code || !formData.discount_value) { - toast({ - title: "Validation Error", - description: "Code and discount value are required.", - variant: "destructive", - }); - return; - } - - setSaving(true); - try { - const codeData = { - code: formData.code.toUpperCase().trim(), - description: formData.description.trim() || null, - discount_type: formData.discount_type, - discount_value: parseFloat(formData.discount_value), - min_order_amount: parseFloat(formData.min_order_amount) || 0, - max_uses: formData.max_uses ? parseInt(formData.max_uses) : null, - valid_until: formData.valid_until ? new Date(formData.valid_until).toISOString() : null, - is_active: formData.is_active, - }; - - if (editingCode) { - const { error } = await supabase - .from("discount_codes") - .update(codeData) - .eq("id", editingCode.id); - - if (error) throw error; - toast({ title: "Success", description: "Discount code updated." }); - } else { - const { error } = await supabase.from("discount_codes").insert(codeData); - if (error) throw error; - toast({ title: "Success", description: "Discount code created." }); - } - - setDialogOpen(false); - resetForm(); - fetchCodes(); - } catch (error: any) { - console.error("Failed to save discount code:", error); - toast({ - title: "Error", - description: error.message || "Failed to save discount code.", - variant: "destructive", - }); - } finally { - setSaving(false); - } - }; - - const handleDelete = async (id: string) => { - if (!confirm("Are you sure you want to delete this discount code?")) return; - - try { - const { error } = await supabase.from("discount_codes").delete().eq("id", id); - if (error) throw error; - toast({ title: "Success", description: "Discount code deleted." }); - fetchCodes(); - } catch (error) { - console.error("Failed to delete discount code:", error); - toast({ - title: "Error", - description: "Failed to delete discount code.", - variant: "destructive", - }); - } - }; - - const copyCode = (code: string) => { - navigator.clipboard.writeText(code); - toast({ title: "Copied!", description: `Code "${code}" copied to clipboard.` }); - }; - - const toggleActive = async (code: DiscountCode) => { - try { - const { error } = await supabase - .from("discount_codes") - .update({ is_active: !code.is_active }) - .eq("id", code.id); - - if (error) throw error; - fetchCodes(); - } catch (error) { - console.error("Failed to toggle code:", error); - } - }; - - const isExpired = (validUntil: string | null) => { - if (!validUntil) return false; - return new Date(validUntil) < new Date(); - }; - - return ( - - - - - Discount Codes - -
- - { setDialogOpen(open); if (!open) resetForm(); }}> - - - - - - {editingCode ? "Edit Discount Code" : "Create Discount Code"} - - {editingCode ? "Update discount code details." : "Create a new discount code for customers."} - - -
-
- -
- setFormData({ ...formData, code: e.target.value.toUpperCase() })} - placeholder="SAVE20" - className="font-mono" - /> - -
-
-
- - setFormData({ ...formData, description: e.target.value })} - placeholder="Summer sale discount" - /> -
-
-
- - -
-
- - setFormData({ ...formData, discount_value: e.target.value })} - placeholder={formData.discount_type === "percentage" ? "20" : "10.00"} - /> -
-
-
-
- - setFormData({ ...formData, min_order_amount: e.target.value })} - placeholder="0" - /> -
-
- - setFormData({ ...formData, max_uses: e.target.value })} - placeholder="Unlimited" - /> -
-
-
-
- - setFormData({ ...formData, valid_until: e.target.value })} - /> -
-
- setFormData({ ...formData, is_active: checked })} - /> - -
-
-
- - - - -
-
-
-
- - {loading ? ( -
- - Loading... -
- ) : codes.length === 0 ? ( -
- -

No discount codes yet

-
- ) : ( - - - - Code - Discount - Uses - Expires - Status - Actions - - - - {codes.map((code) => ( - - -
- {code.code} - -
-
- - {code.discount_type === "percentage" - ? `${code.discount_value}%` - : `$${Number(code.discount_value).toFixed(2)}`} - - - {code.current_uses}{code.max_uses ? `/${code.max_uses}` : ""} - - - {code.valid_until - ? new Date(code.valid_until).toLocaleDateString() - : "Never"} - - - {isExpired(code.valid_until) ? ( - Expired - ) : ( - toggleActive(code)} - > - {code.is_active ? "Active" : "Inactive"} - - )} - - -
- - -
-
-
- ))} -
-
- )} -
-
- ); -}; - -export default DiscountCodesTab; diff --git a/src/components/admin/ProductsTab.tsx b/src/components/admin/ProductsTab.tsx deleted file mode 100644 index 747a8e6..0000000 --- a/src/components/admin/ProductsTab.tsx +++ /dev/null @@ -1,378 +0,0 @@ -import { useState, useEffect } from "react"; -import { supabase } from "@/integrations/supabase/client"; -import { useToast } from "@/hooks/use-toast"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; -import { Switch } from "@/components/ui/switch"; -import { Badge } from "@/components/ui/badge"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Plus, Pencil, Trash2, Package, RefreshCw } from "lucide-react"; - -interface Product { - id: string; - sku: string; - name: string; - description: string | null; - price: number; - currency: string; - download_url: string | null; - is_active: boolean; - stock_quantity: number | null; - created_at: string; -} - -const ProductsTab = () => { - const { toast } = useToast(); - const [products, setProducts] = useState([]); - const [loading, setLoading] = useState(true); - const [dialogOpen, setDialogOpen] = useState(false); - const [editingProduct, setEditingProduct] = useState(null); - const [saving, setSaving] = useState(false); - - const [formData, setFormData] = useState({ - sku: "", - name: "", - description: "", - price: "", - currency: "USD", - download_url: "", - is_active: true, - stock_quantity: "", - }); - - useEffect(() => { - fetchProducts(); - }, []); - - const fetchProducts = async () => { - setLoading(true); - try { - const { data, error } = await supabase - .from("products") - .select("*") - .order("created_at", { ascending: false }); - - if (error) throw error; - setProducts(data || []); - } catch (error) { - console.error("Failed to fetch products:", error); - toast({ - title: "Error", - description: "Failed to load products.", - variant: "destructive", - }); - } finally { - setLoading(false); - } - }; - - const resetForm = () => { - setFormData({ - sku: "", - name: "", - description: "", - price: "", - currency: "USD", - download_url: "", - is_active: true, - stock_quantity: "", - }); - setEditingProduct(null); - }; - - const openEditDialog = (product: Product) => { - setEditingProduct(product); - setFormData({ - sku: product.sku, - name: product.name, - description: product.description || "", - price: product.price.toString(), - currency: product.currency, - download_url: product.download_url || "", - is_active: product.is_active, - stock_quantity: product.stock_quantity?.toString() || "", - }); - setDialogOpen(true); - }; - - const handleSubmit = async () => { - if (!formData.sku || !formData.name || !formData.price) { - toast({ - title: "Validation Error", - description: "SKU, name, and price are required.", - variant: "destructive", - }); - return; - } - - setSaving(true); - try { - const productData = { - sku: formData.sku.trim(), - name: formData.name.trim(), - description: formData.description.trim() || null, - price: parseFloat(formData.price), - currency: formData.currency, - download_url: formData.download_url.trim() || null, - is_active: formData.is_active, - stock_quantity: formData.stock_quantity ? parseInt(formData.stock_quantity) : null, - }; - - if (editingProduct) { - const { error } = await supabase - .from("products") - .update(productData) - .eq("id", editingProduct.id); - - if (error) throw error; - toast({ title: "Success", description: "Product updated." }); - } else { - const { error } = await supabase.from("products").insert(productData); - if (error) throw error; - toast({ title: "Success", description: "Product created." }); - } - - setDialogOpen(false); - resetForm(); - fetchProducts(); - } catch (error: any) { - console.error("Failed to save product:", error); - toast({ - title: "Error", - description: error.message || "Failed to save product.", - variant: "destructive", - }); - } finally { - setSaving(false); - } - }; - - const handleDelete = async (id: string) => { - if (!confirm("Are you sure you want to delete this product?")) return; - - try { - const { error } = await supabase.from("products").delete().eq("id", id); - if (error) throw error; - toast({ title: "Success", description: "Product deleted." }); - fetchProducts(); - } catch (error) { - console.error("Failed to delete product:", error); - toast({ - title: "Error", - description: "Failed to delete product.", - variant: "destructive", - }); - } - }; - - const toggleActive = async (product: Product) => { - try { - const { error } = await supabase - .from("products") - .update({ is_active: !product.is_active }) - .eq("id", product.id); - - if (error) throw error; - fetchProducts(); - } catch (error) { - console.error("Failed to toggle product:", error); - } - }; - - return ( - - - - - Products - -
- - { setDialogOpen(open); if (!open) resetForm(); }}> - - - - - - {editingProduct ? "Edit Product" : "Add Product"} - - {editingProduct ? "Update product details." : "Create a new product."} - - -
-
-
- - setFormData({ ...formData, sku: e.target.value })} - placeholder="prod_001" - /> -
-
- - setFormData({ ...formData, price: e.target.value })} - placeholder="49.99" - /> -
-
-
- - setFormData({ ...formData, name: e.target.value })} - placeholder="Product Name" - /> -
-
- -