ecom / client /src /pages /admin-dev.tsx
shashwatIDR's picture
Upload 106 files
1684141 verified
import { useState, useEffect } from "react";
import { useLocation } from "wouter";
import { useAuth } from "@/hooks/use-auth";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { adminApi, categoriesApi, productsApi } from "@/lib/api";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
import { useToast } from "@/hooks/use-toast";
import { Users, Store, Package, Settings, Plus, Trash2, Edit, Eye, ShoppingCart, MapPin, ExternalLink } from "lucide-react";
import { useIsMobile } from "@/hooks/use-mobile";
const sellerSchema = z.object({
username: z.string().min(3, "Username must be at least 3 characters"),
password: z.string().min(6, "Password must be at least 6 characters"),
});
const categorySchema = z.object({
name: z.string().min(1, "Category name is required"),
icon: z.string().optional(),
imageUrl: z.string().url().optional().or(z.literal("")),
});
const productSchema = z.object({
title: z.string().min(1, "Product title is required"),
description: z.string().min(1, "Product description is required"),
price: z.string().min(1, "Price is required"),
originalPrice: z.string().optional(),
stock: z.string().min(1, "Stock is required"),
categoryId: z.string().min(1, "Category is required"),
isActive: z.boolean().default(true),
});
type SellerFormData = z.infer<typeof sellerSchema>;
type CategoryFormData = z.infer<typeof categorySchema>;
type ProductFormData = z.infer<typeof productSchema>;
export default function AdminDev() {
const [, setLocation] = useLocation();
const { userType, adminLogin } = useAuth();
const { toast } = useToast();
const queryClient = useQueryClient();
const [isSellerDialogOpen, setIsSellerDialogOpen] = useState(false);
const [isCategoryDialogOpen, setIsCategoryDialogOpen] = useState(false);
const [editingCategory, setEditingCategory] = useState<any>(null);
const [editingProduct, setEditingProduct] = useState<any>(null);
const [isProductDialogOpen, setIsProductDialogOpen] = useState(false);
const [activeTab, setActiveTab] = useState("sellers");
const isMobile = useIsMobile();
// Auto-login as admin if not already
useEffect(() => {
if (userType !== 'admin') {
adminLogin();
}
}, [userType, adminLogin]);
const { data: sellers = [], isLoading: sellersLoading } = useQuery({
queryKey: ['/api/admin/sellers'],
queryFn: () => adminApi.getSellers(),
});
const { data: categories = [], isLoading: categoriesLoading } = useQuery({
queryKey: ['/api/categories'],
queryFn: () => categoriesApi.getAll(),
});
const { data: products = [], isLoading: productsLoading } = useQuery({
queryKey: ['/api/products'],
queryFn: () => productsApi.getAll(),
});
const { data: orders = [], isLoading: ordersLoading } = useQuery({
queryKey: ['/api/admin/orders'],
queryFn: () => adminApi.getOrders(),
});
const { data: users = [], isLoading: usersLoading } = useQuery({
queryKey: ['/api/admin/users'],
queryFn: async () => {
const response = await fetch('/api/admin/users');
return response.json();
},
});
const sellerForm = useForm<SellerFormData>({
resolver: zodResolver(sellerSchema),
defaultValues: {
username: "",
password: "",
},
});
const categoryForm = useForm<CategoryFormData>({
resolver: zodResolver(categorySchema),
defaultValues: {
name: "",
icon: "",
imageUrl: "",
},
});
const productForm = useForm<ProductFormData>({
resolver: zodResolver(productSchema),
defaultValues: {
title: "",
description: "",
price: "",
originalPrice: "",
stock: "",
categoryId: "",
isActive: true,
},
});
const createSellerMutation = useMutation({
mutationFn: (data: SellerFormData) => adminApi.createSeller(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/admin/sellers'] });
setIsSellerDialogOpen(false);
sellerForm.reset();
toast({
title: "Seller created",
description: "New seller account has been created successfully.",
});
},
onError: () => {
toast({
variant: "destructive",
title: "Error",
description: "Failed to create seller account.",
});
},
});
const deleteSellerMutation = useMutation({
mutationFn: (id: string) => adminApi.deleteSeller(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/admin/sellers'] });
toast({
title: "Seller deleted",
description: "Seller account and all associated products have been deleted.",
});
},
onError: () => {
toast({
variant: "destructive",
title: "Error",
description: "Failed to delete seller account.",
});
},
});
const createCategoryMutation = useMutation({
mutationFn: (data: CategoryFormData) => categoriesApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/categories'] });
setIsCategoryDialogOpen(false);
categoryForm.reset();
toast({
title: "Category created",
description: "New category has been created successfully.",
});
},
onError: () => {
toast({
variant: "destructive",
title: "Error",
description: "Failed to create category.",
});
},
});
const deleteCategoryMutation = useMutation({
mutationFn: (id: string) => categoriesApi.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/categories'] });
toast({
title: "Category deleted",
description: "Category has been deleted successfully.",
});
},
onError: () => {
toast({
variant: "destructive",
title: "Error",
description: "Failed to delete category.",
});
},
});
const updateCategoryMutation = useMutation({
mutationFn: ({ id, data }: { id: string; data: CategoryFormData }) =>
categoriesApi.update(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/categories'] });
setEditingCategory(null);
setIsCategoryDialogOpen(false);
categoryForm.reset();
toast({
title: "Category updated",
description: "Category has been updated successfully.",
});
},
onError: () => {
toast({
variant: "destructive",
title: "Error",
description: "Failed to update category.",
});
},
});
const updateProductMutation = useMutation({
mutationFn: ({ id, data }: { id: string; data: ProductFormData }) =>
adminApi.updateProduct(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/products'] });
setEditingProduct(null);
setIsProductDialogOpen(false);
productForm.reset();
toast({
title: "Product updated",
description: "Product has been updated successfully.",
});
},
onError: () => {
toast({
variant: "destructive",
title: "Error",
description: "Failed to update product.",
});
},
});
const deleteProductMutation = useMutation({
mutationFn: (id: string) => adminApi.deleteProduct(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/products'] });
toast({
title: "Product deleted",
description: "Product has been deleted successfully.",
});
},
onError: () => {
toast({
variant: "destructive",
title: "Error",
description: "Failed to delete product.",
});
},
});
const updateOrderMutation = useMutation({
mutationFn: ({ id, data }: { id: string; data: any }) => adminApi.updateOrder(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/api/admin/orders'] });
toast({
title: "Order updated",
description: "Order status has been updated successfully.",
});
},
onError: () => {
toast({
variant: "destructive",
title: "Error",
description: "Failed to update order status.",
});
},
});
const handleUpdateOrderStatus = (id: string, data: any) => {
updateOrderMutation.mutate({ id, data });
};
const onCreateSeller = (data: SellerFormData) => {
createSellerMutation.mutate(data);
};
const onCreateCategory = (data: CategoryFormData) => {
createCategoryMutation.mutate(data);
};
const onEditCategory = (data: CategoryFormData) => {
if (editingCategory) {
updateCategoryMutation.mutate({ id: editingCategory.id, data });
}
};
const onEditProduct = (data: ProductFormData) => {
if (editingProduct) {
updateProductMutation.mutate({ id: editingProduct.id, data });
}
};
const handleEditCategory = (category: any) => {
setEditingCategory(category);
categoryForm.reset({
name: category.name,
icon: category.icon || "",
imageUrl: category.imageUrl || "",
});
setIsCategoryDialogOpen(true);
};
const handleEditProduct = (product: any) => {
setEditingProduct(product);
productForm.reset({
title: product.title,
description: product.description,
price: product.price.toString(),
originalPrice: product.originalPrice ? product.originalPrice.toString() : "",
stock: product.stock.toString(),
categoryId: product.categoryId,
isActive: product.isActive,
});
setIsProductDialogOpen(true);
};
const handleCancelEdit = () => {
setEditingCategory(null);
setEditingProduct(null);
setIsCategoryDialogOpen(false);
setIsProductDialogOpen(false);
categoryForm.reset();
productForm.reset();
};
return (
<div className="min-h-screen page-gradient pb-24 md:pb-0">
<header className="glass-strong border-0 shadow-2xl sticky top-0 z-40">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<div>
<h1 className="text-2xl font-bold text-primary" data-testid="admin-title">
Shoposphere Admin
</h1>
<p className="text-sm text-muted-foreground">Developer Administration Panel</p>
</div>
<Button onClick={() => setLocation('/')} variant="outline" data-testid="back-to-site">
Back to Site
</Button>
</div>
</div>
</header>
<div className="max-w-7xl mx-auto px-3 sm:px-4 md:px-6 lg:px-8 py-4 md:py-6 lg:py-8" data-testid="admin-dev-page">
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4 md:space-y-6">
<TabsContent value="sellers">
<Card className="glass-card border-0">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Seller Management</CardTitle>
<Dialog open={isSellerDialogOpen} onOpenChange={setIsSellerDialogOpen}>
<DialogTrigger asChild>
<Button data-testid="create-seller-button">
<Plus className="mr-2 h-4 w-4" />
Create Seller
</Button>
</DialogTrigger>
<DialogContent className="bg-white border border-black">
<DialogHeader>
<DialogTitle>Create New Seller</DialogTitle>
</DialogHeader>
<Form {...sellerForm}>
<form onSubmit={sellerForm.handleSubmit(onCreateSeller)} className="space-y-4">
<FormField
control={sellerForm.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input {...field} data-testid="seller-username-input" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={sellerForm.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input {...field} type="password" data-testid="seller-password-input" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="w-full" data-testid="submit-seller">
Create Seller
</Button>
</form>
</Form>
</DialogContent>
</Dialog>
</CardHeader>
<CardContent>
{sellersLoading ? (
<div className="space-y-4">
{[...Array(3)].map((_, i) => (
<div key={i} className="flex items-center justify-between p-4 glass border-0 rounded-2xl">
<Skeleton className="h-6 w-48" />
<Skeleton className="h-8 w-20" />
</div>
))}
</div>
) : sellers.length > 0 ? (
<div className="space-y-4" data-testid="sellers-list">
{sellers.map((seller: any) => (
<div key={seller.id} className="flex items-center justify-between p-4 glass border-0 rounded-2xl hover:scale-105 transition-all duration-300" data-testid={`seller-${seller.id}`}>
<div>
<h3 className="font-medium text-foreground" data-testid={`seller-username-${seller.id}`}>
{seller.username}
</h3>
<p className="text-sm text-muted-foreground" data-testid={`seller-created-${seller.id}`}>
Created: {new Date(seller.createdAt).toLocaleDateString()}
</p>
<div className="mt-2 p-2 bg-yellow-50 dark:bg-yellow-900/20 rounded border">
<p className="text-xs font-medium text-yellow-800 dark:text-yellow-200 mb-1">Login Credentials:</p>
<p className="text-xs text-yellow-700 dark:text-yellow-300" data-testid={`seller-credentials-${seller.id}`}>
<span className="font-medium">Username:</span> {seller.username}<br/>
<span className="font-medium">Password:</span> {seller.plainTextPassword || 'Not available'}
</p>
</div>
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => setLocation(`/admin-prospective=seller`)}
data-testid={`view-seller-${seller.id}`}
>
<Eye className="h-4 w-4" />
</Button>
<Button
variant="destructive"
size="sm"
onClick={() => deleteSellerMutation.mutate(seller.id)}
data-testid={`delete-seller-${seller.id}`}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-12" data-testid="no-sellers">
<Users className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<p className="text-muted-foreground">No sellers created yet.</p>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="users">
<Card className="glass-card border-0">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<MapPin className="h-5 w-5" />
User Locations & Information
</CardTitle>
</CardHeader>
<CardContent>
{usersLoading ? (
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div key={i} className="flex items-center justify-between p-4 glass border-0 rounded-2xl">
<Skeleton className="h-6 w-48" />
<Skeleton className="h-8 w-20" />
</div>
))}
</div>
) : users.length > 0 ? (
<div className="space-y-4" data-testid="users-list">
{users.map((user: any) => (
<div key={user.id} className="flex items-start justify-between p-4 glass border-0 rounded-2xl hover:scale-[1.02] transition-all duration-300" data-testid={`user-${user.id}`}>
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h3 className="font-medium text-foreground" data-testid={`user-name-${user.id}`}>
{user.firstName} {user.lastName}
</h3>
<Badge variant="secondary" className="text-xs">
@{user.username}
</Badge>
</div>
<div className="space-y-1 text-sm text-muted-foreground mb-3">
<p data-testid={`user-contact-${user.id}`}>
📧 {user.email} • 📞 {user.phone}
</p>
<p className="text-xs">
Joined: {new Date(user.createdAt).toLocaleDateString()}
</p>
</div>
{/* Location Information */}
<div className="mt-3 p-3 bg-gray-50 dark:bg-gray-800/50 rounded-lg">
{user.locationDetectedAutomatically && user.googleMapsUrl ? (
<div className="space-y-2">
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4 text-green-600" />
<span className="text-sm font-medium text-green-600" data-testid={`user-location-auto-${user.id}`}>
Auto-detected location
</span>
</div>
<div className="flex items-center gap-2 text-xs text-gray-600 dark:text-gray-400">
<span data-testid={`coordinates-${user.id}`}>
📍 {Number(user.latitude).toFixed(4)}, {Number(user.longitude).toFixed(4)}
</span>
</div>
<Button
size="sm"
variant="outline"
className="h-7 px-2 text-xs"
onClick={() => window.open(user.googleMapsUrl, '_blank')}
data-testid={`view-location-${user.id}`}
>
<ExternalLink className="mr-1 h-3 w-3" />
View on Google Maps
</Button>
</div>
) : user.street && user.city ? (
<div className="space-y-2">
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4 text-blue-600" />
<span className="text-sm font-medium text-blue-600">
Manual address
</span>
</div>
<p className="text-sm text-gray-600 dark:text-gray-400" data-testid={`user-location-manual-${user.id}`}>
{user.street}, {user.city}, {user.state} {user.pinCode}, {user.country}
</p>
</div>
) : (
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4 text-gray-400" />
<span className="text-sm text-gray-400" data-testid={`user-location-none-${user.id}`}>
No location provided
</span>
</div>
)}
</div>
</div>
<div className="text-right text-xs text-muted-foreground">
<p>User ID: {user.id.slice(-8)}</p>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-12" data-testid="no-users">
<Users className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<p className="text-muted-foreground">No users registered yet.</p>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="categories">
<Card className="glass-card border-0">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Category Management</CardTitle>
<Dialog open={isCategoryDialogOpen} onOpenChange={(open) => {
setIsCategoryDialogOpen(open);
if (!open) handleCancelEdit();
}}>
<DialogTrigger asChild>
<Button data-testid="create-category-button">
<Plus className="mr-2 h-4 w-4" />
Create Category
</Button>
</DialogTrigger>
<DialogContent className="bg-white border border-black">
<DialogHeader>
<DialogTitle>{editingCategory ? 'Edit Category' : 'Create New Category'}</DialogTitle>
</DialogHeader>
<Form {...categoryForm}>
<form onSubmit={categoryForm.handleSubmit(editingCategory ? onEditCategory : onCreateCategory)} className="space-y-4">
<FormField
control={categoryForm.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Category Name</FormLabel>
<FormControl>
<Input {...field} data-testid="category-name-input" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={categoryForm.control}
name="icon"
render={({ field }) => (
<FormItem>
<FormLabel>Icon (optional)</FormLabel>
<FormControl>
<Input {...field} placeholder="fa-laptop" data-testid="category-icon-input" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={categoryForm.control}
name="imageUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Image URL (optional)</FormLabel>
<FormControl>
<Input {...field} placeholder="https://example.com/image.jpg" data-testid="category-image-input" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex space-x-2">
<Button
type="submit"
className="flex-1"
data-testid="submit-category"
disabled={updateCategoryMutation.isPending || createCategoryMutation.isPending}
>
{editingCategory ? 'Update Category' : 'Create Category'}
</Button>
{editingCategory && (
<Button
type="button"
variant="outline"
onClick={handleCancelEdit}
data-testid="cancel-edit-category"
>
Cancel
</Button>
)}
</div>
</form>
</Form>
</DialogContent>
</Dialog>
</CardHeader>
<CardContent>
{categoriesLoading ? (
<div className="space-y-4">
{[...Array(3)].map((_, i) => (
<div key={i} className="flex items-center justify-between p-4 glass border-0 rounded-2xl">
<Skeleton className="h-6 w-48" />
<Skeleton className="h-8 w-20" />
</div>
))}
</div>
) : categories.length > 0 ? (
<div className="space-y-4" data-testid="categories-list">
{categories.map((category: any) => (
<div key={category.id} className="flex items-center justify-between p-4 glass border-0 rounded-2xl hover:scale-105 transition-all duration-300" data-testid={`category-${category.id}`}>
<div className="flex items-center space-x-3">
{category.imageUrl && (
<div className="w-10 h-10 rounded-full overflow-hidden bg-gray-100 flex-shrink-0">
<img
src={category.imageUrl}
alt={category.name}
className="w-full h-full object-cover"
/>
</div>
)}
<div>
<h3 className="font-medium text-foreground" data-testid={`category-name-${category.id}`}>
{category.name}
</h3>
{category.icon && (
<p className="text-sm text-muted-foreground" data-testid={`category-icon-${category.id}`}>
Icon: {category.icon}
</p>
)}
{category.imageUrl && (
<p className="text-xs text-muted-foreground truncate max-w-48" data-testid={`category-image-${category.id}`}>
Image: {category.imageUrl}
</p>
)}
</div>
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => handleEditCategory(category)}
data-testid={`edit-category-${category.id}`}
>
<Edit className="h-4 w-4" />
</Button>
<Button
variant="destructive"
size="sm"
onClick={() => deleteCategoryMutation.mutate(category.id)}
data-testid={`delete-category-${category.id}`}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-12" data-testid="no-categories">
<Store className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<p className="text-muted-foreground">No categories created yet.</p>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="products">
<Card className="glass-card border-0">
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle>Product Management</CardTitle>
<Dialog open={isProductDialogOpen} onOpenChange={(open) => {
setIsProductDialogOpen(open);
if (!open) handleCancelEdit();
}}>
<DialogTrigger asChild>
<Button variant="outline" data-testid="edit-products-button">
<Edit className="mr-2 h-4 w-4" />
Edit Mode
</Button>
</DialogTrigger>
<DialogContent className="bg-white border border-black max-w-2xl mx-4 sm:mx-auto">
<DialogHeader>
<DialogTitle>Edit Product</DialogTitle>
</DialogHeader>
{editingProduct && (
<Form {...productForm}>
<form onSubmit={productForm.handleSubmit(onEditProduct)} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<FormField
control={productForm.control}
name="title"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Product Title</FormLabel>
<FormControl>
<Input {...field} data-testid="product-title-input" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={productForm.control}
name="description"
render={({ field }) => (
<FormItem className="col-span-2">
<FormLabel>Description</FormLabel>
<FormControl>
<Input {...field} data-testid="product-description-input" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={productForm.control}
name="price"
render={({ field }) => (
<FormItem>
<FormLabel>Price</FormLabel>
<FormControl>
<Input {...field} type="number" step="0.01" data-testid="product-price-input" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={productForm.control}
name="originalPrice"
render={({ field }) => (
<FormItem>
<FormLabel>Original Price (optional)</FormLabel>
<FormControl>
<Input {...field} type="number" step="0.01" data-testid="product-original-price-input" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={productForm.control}
name="stock"
render={({ field }) => (
<FormItem>
<FormLabel>Stock</FormLabel>
<FormControl>
<Input {...field} type="number" data-testid="product-stock-input" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={productForm.control}
name="categoryId"
render={({ field }) => (
<FormItem>
<FormLabel>Category</FormLabel>
<FormControl>
<select {...field} className="w-full p-2 border rounded" data-testid="product-category-select">
<option value="">Select a category</option>
{categories.map((category: any) => (
<option key={category.id} value={category.id}>
{category.name}
</option>
))}
</select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex space-x-2">
<Button
type="submit"
className="flex-1"
data-testid="submit-product"
disabled={updateProductMutation.isPending}
>
Update Product
</Button>
<Button
type="button"
variant="outline"
onClick={handleCancelEdit}
data-testid="cancel-edit-product"
>
Cancel
</Button>
</div>
</form>
</Form>
)}
</DialogContent>
</Dialog>
</CardHeader>
<CardContent>
{productsLoading ? (
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<div key={i} className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex items-center space-x-4">
<Skeleton className="w-16 h-16" />
<div>
<Skeleton className="h-6 w-48 mb-2" />
<Skeleton className="h-4 w-32" />
</div>
</div>
<Skeleton className="h-8 w-20" />
</div>
))}
</div>
) : products.length > 0 ? (
<div className="space-y-4" data-testid="products-list">
{products.map((product: any) => (
<div key={product.id} className="flex items-center justify-between p-4 glass border-0 rounded-2xl hover:scale-105 transition-all duration-300" data-testid={`product-${product.id}`}>
<div className="flex items-center space-x-4">
<div className="w-16 h-16 bg-gray-100 rounded overflow-hidden">
{product.images && product.images.length > 0 ? (
<img
src={product.images[0]}
alt={product.title}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-gray-200 flex items-center justify-center">
<span className="text-xs text-gray-400">No Image</span>
</div>
)}
</div>
<div>
<h3 className="font-medium text-gray-900" data-testid={`product-title-${product.id}`}>
{product.title}
</h3>
<p className="text-sm text-gray-500" data-testid={`product-seller-${product.id}`}>
By: {product.seller?.username || 'Unknown Seller'}
</p>
<div className="flex items-center space-x-2 mt-1">
<span className="text-sm font-semibold text-gray-900" data-testid={`product-price-${product.id}`}>
₹{parseFloat(product.price).toFixed(2)}
</span>
<Badge variant={product.isActive ? 'default' : 'secondary'} data-testid={`product-status-${product.id}`}>
{product.isActive ? 'Active' : 'Inactive'}
</Badge>
</div>
</div>
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => setLocation(`/product/${product.id}`)}
data-testid={`view-product-${product.id}`}
>
<Eye className="h-4 w-4" />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleEditProduct(product)}
data-testid={`edit-product-${product.id}`}
>
<Edit className="h-4 w-4" />
</Button>
<Button
variant="destructive"
size="sm"
onClick={() => deleteProductMutation.mutate(product.id)}
data-testid={`delete-product-${product.id}`}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
) : (
<div className="text-center py-12" data-testid="no-products">
<Package className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-500">No products available.</p>
</div>
)}
</CardContent>
</Card>
</TabsContent>
<TabsContent value="settings">
<Card className="glass-card border-0">
<CardHeader>
<CardTitle>System Settings</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-6" data-testid="settings-panel">
<div>
<h3 className="text-lg font-medium text-foreground mb-2">Platform Statistics</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3 md:gap-4">
<div className="glass-card p-4 rounded-2xl">
<div className="text-2xl font-bold text-primary" data-testid="total-sellers">
{sellers.length}
</div>
<div className="text-sm text-muted-foreground">Total Sellers</div>
</div>
<div className="glass-card p-4 rounded-2xl">
<div className="text-2xl font-bold text-primary" data-testid="total-products">
{products.length}
</div>
<div className="text-sm text-muted-foreground">Total Products</div>
</div>
<div className="glass-card p-4 rounded-2xl">
<div className="text-2xl font-bold text-primary" data-testid="total-categories">
{categories.length}
</div>
<div className="text-sm text-muted-foreground">Total Categories</div>
</div>
</div>
</div>
<Separator />
<div>
<h3 className="text-lg font-medium text-foreground mb-2">Quick Actions</h3>
<div className="flex flex-wrap gap-2 sm:gap-4">
<Button variant="outline" size={isMobile ? "sm" : "default"} data-testid="backup-data">
Backup Data
</Button>
<Button variant="outline" size={isMobile ? "sm" : "default"} data-testid="export-reports">
Export Reports
</Button>
<Button variant="outline" size={isMobile ? "sm" : "default"} data-testid="system-logs">
View System Logs
</Button>
</div>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="orders">
<Card className="glass-card border-0">
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<ShoppingCart className="h-5 w-5" />
<span>Order Management</span>
</CardTitle>
</CardHeader>
<CardContent>
{ordersLoading ? (
<div className="space-y-4">
{[...Array(5)].map((_, i) => (
<Skeleton key={i} className="h-32 w-full" />
))}
</div>
) : orders.length === 0 ? (
<div className="text-center py-12">
<ShoppingCart className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-500">No orders found.</p>
</div>
) : (
<div className="space-y-4" data-testid="admin-orders-list">
{orders.map((order: any) => (
<div key={order.id} className="border border-gray-200 rounded-lg p-6" data-testid={`admin-order-${order.id}`}>
<div className="flex items-start justify-between mb-4">
<div>
<h3 className="font-semibold text-lg">
Order #{order.id.slice(-8)}
</h3>
<p className="text-sm text-gray-600">
Placed on {new Date(order.createdAt).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</p>
</div>
<div className="text-right">
<div className="flex items-center space-x-2 mb-2">
<Badge
variant={
order.status === 'delivered' ? 'default' :
order.status === 'shipped' ? 'secondary' :
order.status === 'pending' ? 'outline' :
'destructive'
}
>
{order.status}
</Badge>
<Badge variant={order.paymentStatus === 'paid' ? 'default' : 'destructive'}>
{order.paymentStatus === 'paid' ? 'Paid' : 'Unpaid'}
</Badge>
</div>
<p className="text-lg font-semibold">
₹{parseFloat(order.total).toFixed(2)}
</p>
</div>
</div>
{/* Customer Info */}
<div className="bg-gray-50 rounded-lg p-4 mb-4">
<h4 className="font-medium mb-2">Customer Information</h4>
{order.user && (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-4 mb-3">
<div className="space-y-1">
<p className="text-sm"><span className="font-medium">Name:</span> {order.user.firstName} {order.user.lastName}</p>
<p className="text-sm"><span className="font-medium">Email:</span> {order.user.email}</p>
</div>
<div className="space-y-1">
<p className="text-sm"><span className="font-medium">Phone:</span> {order.user.phone}</p>
<p className="text-sm"><span className="font-medium">Payment:</span> {order.paymentMethod === 'cod' ? 'Cash on Delivery' : 'UPI'}</p>
</div>
</div>
)}
<p className="text-sm text-gray-600"><span className="font-medium">Address:</span> {order.shippingAddress}</p>
</div>
{/* Admin Order Management Actions */}
<div className="flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-2 mb-4 p-3 bg-red-50 rounded-lg">
<div className="flex items-center space-x-2">
<label className="text-sm font-medium whitespace-nowrap">Status:</label>
<select
className="flex-1 px-3 py-1 text-sm border rounded min-w-0"
value={order.status}
onChange={(e) => handleUpdateOrderStatus(order.id, { status: e.target.value })}
>
<option value="pending">Pending</option>
<option value="processing">Processing</option>
<option value="shipped">Shipped</option>
<option value="delivered">Delivered</option>
<option value="cancelled">Cancelled</option>
</select>
</div>
<Button
size="sm"
variant={order.paymentStatus === 'paid' ? 'outline' : 'default'}
onClick={() => handleUpdateOrderStatus(order.id, {
paymentStatus: order.paymentStatus === 'paid' ? 'unpaid' : 'paid'
})}
className="w-full sm:w-auto"
>
Mark as {order.paymentStatus === 'paid' ? 'Unpaid' : 'Paid'}
</Button>
</div>
{/* Order Items */}
<div>
<h4 className="font-medium mb-3">Items Ordered</h4>
<div className="space-y-2">
{order.items?.map((item: any, index: number) => (
<div key={index} className="flex items-center justify-between py-2 border-b border-gray-100 last:border-0">
<div className="flex items-center space-x-3">
<div className="w-12 h-12 bg-gray-100 rounded overflow-hidden">
{item.product?.images?.[0] ? (
<img
src={item.product.images[0]}
alt={item.product.title}
className="w-full h-full object-cover"
/>
) : (
<div className="w-full h-full bg-gray-200 flex items-center justify-center">
<Package className="h-4 w-4 text-gray-400" />
</div>
)}
</div>
<div>
<p className="font-medium text-sm">{item.product?.title}</p>
<p className="text-xs text-gray-500">Qty: {item.quantity}</p>
</div>
</div>
<div className="text-right">
<p className="font-medium text-sm">₹{parseFloat(item.price).toFixed(2)}</p>
<p className="text-xs text-gray-500">₹{(parseFloat(item.price) * item.quantity).toFixed(2)} total</p>
</div>
</div>
))}
</div>
</div>
<div className="flex justify-end mt-4 pt-4 border-t">
<div className="text-right">
<p className="text-sm text-gray-600">Subtotal: ₹{parseFloat(order.subtotal).toFixed(2)}</p>
<p className="text-sm text-gray-600">Tax: ₹{parseFloat(order.tax).toFixed(2)}</p>
<p className="text-sm text-gray-600">Shipping: ₹{parseFloat(order.shipping || '0').toFixed(2)}</p>
<p className="font-semibold text-lg">Total: ₹{parseFloat(order.total).toFixed(2)}</p>
</div>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
{/* Floating Navigation for All Screen Sizes */}
<div className="fixed bottom-6 left-1/2 transform -translate-x-1/2 z-50">
<nav className="bg-white/80 backdrop-blur-xl border border-gray-200/30 rounded-2xl shadow-lg px-4 py-3 flex items-center space-x-2">
<button
onClick={() => setActiveTab("sellers")}
className={`relative flex flex-col items-center justify-center p-3 rounded-xl transition-all duration-200 ${
activeTab === "sellers"
? 'text-purple-600 bg-purple-50'
: 'text-gray-500 hover:text-purple-600 hover:bg-gray-50'
}`}
data-testid="mobile-nav-sellers"
>
<Users className="h-5 w-5" />
{activeTab === "sellers" && (
<div className="absolute top-1/2 left-0 transform -translate-y-1/2 w-1 h-6 bg-purple-600 rounded-r-full"></div>
)}
</button>
<button
onClick={() => setActiveTab("users")}
className={`relative flex flex-col items-center justify-center p-3 rounded-xl transition-all duration-200 ${
activeTab === "users"
? 'text-purple-600 bg-purple-50'
: 'text-gray-500 hover:text-purple-600 hover:bg-gray-50'
}`}
data-testid="mobile-nav-users"
>
<MapPin className="h-5 w-5" />
{activeTab === "users" && (
<div className="absolute top-1/2 left-0 transform -translate-y-1/2 w-1 h-6 bg-purple-600 rounded-r-full"></div>
)}
</button>
<button
onClick={() => setActiveTab("categories")}
className={`relative flex flex-col items-center justify-center p-3 rounded-xl transition-all duration-200 ${
activeTab === "categories"
? 'text-purple-600 bg-purple-50'
: 'text-gray-500 hover:text-purple-600 hover:bg-gray-50'
}`}
data-testid="mobile-nav-categories"
>
<Store className="h-5 w-5" />
{activeTab === "categories" && (
<div className="absolute top-1/2 left-0 transform -translate-y-1/2 w-1 h-6 bg-purple-600 rounded-r-full"></div>
)}
</button>
<button
onClick={() => setActiveTab("products")}
className={`relative flex flex-col items-center justify-center p-3 rounded-xl transition-all duration-200 ${
activeTab === "products"
? 'text-purple-600 bg-purple-50'
: 'text-gray-500 hover:text-purple-600 hover:bg-gray-50'
}`}
data-testid="mobile-nav-products"
>
<Package className="h-5 w-5" />
{activeTab === "products" && (
<div className="absolute top-1/2 left-0 transform -translate-y-1/2 w-1 h-6 bg-purple-600 rounded-r-full"></div>
)}
</button>
<button
onClick={() => setActiveTab("orders")}
className={`relative flex flex-col items-center justify-center p-3 rounded-xl transition-all duration-200 ${
activeTab === "orders"
? 'text-purple-600 bg-purple-50'
: 'text-gray-500 hover:text-purple-600 hover:bg-gray-50'
}`}
data-testid="mobile-nav-orders"
>
<ShoppingCart className="h-5 w-5" />
{activeTab === "orders" && (
<div className="absolute top-1/2 left-0 transform -translate-y-1/2 w-1 h-6 bg-purple-600 rounded-r-full"></div>
)}
</button>
<button
onClick={() => setActiveTab("settings")}
className={`relative flex flex-col items-center justify-center p-3 rounded-xl transition-all duration-200 ${
activeTab === "settings"
? 'text-purple-600 bg-purple-50'
: 'text-gray-500 hover:text-purple-600 hover:bg-gray-50'
}`}
data-testid="mobile-nav-settings"
>
<Settings className="h-5 w-5" />
{activeTab === "settings" && (
<div className="absolute top-1/2 left-0 transform -translate-y-1/2 w-1 h-6 bg-purple-600 rounded-r-full"></div>
)}
</button>
</nav>
</div>
</div>
);
}