Improve browser detection and table UX for Firefox
All checks were successful
Build Frontend / build (push) Successful in 1m10s
All checks were successful
Build Frontend / build (push) Successful in 1m10s
Standardizes browser detection logic across admin and storefront pages to more accurately identify Firefox. Updates table rendering logic to provide better compatibility and fallback for Firefox, including conditional use of AnimatePresence and improved loading/empty states. Refines table UI styles for consistency and accessibility.
This commit is contained in:
@@ -50,6 +50,14 @@ export default function AdminUsersPage() {
|
||||
const { toast } = useToast();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [users, setUsers] = useState<TelegramUser[]>([]);
|
||||
// State for browser detection
|
||||
// Browser detection
|
||||
const [isFirefox, setIsFirefox] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
setIsFirefox(ua.includes("firefox") && !ua.includes("chrome"));
|
||||
}, []);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [page, setPage] = useState(1);
|
||||
const [pagination, setPagination] = useState<PaginationResponse['pagination'] | null>(null);
|
||||
@@ -198,20 +206,14 @@ export default function AdminUsersPage() {
|
||||
<AnimatePresence mode="popLayout">
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="h-32 text-center">
|
||||
<div className="flex flex-col items-center justify-center gap-2 text-muted-foreground">
|
||||
<Loader2 className="h-8 w-8 animate-spin opacity-25" />
|
||||
<p>Loading users...</p>
|
||||
<TableCell colSpan={8} className="h-24 text-center">
|
||||
<div className="flex items-center justify-center gap-2 text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
Loading users...
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : users.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="h-32 text-center text-muted-foreground">
|
||||
{searchQuery ? "No users found matching your search" : "No users found"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
) : users.length > 0 ? (
|
||||
users.map((user, index) => (
|
||||
<motion.tr
|
||||
key={user.telegramUserId}
|
||||
@@ -219,84 +221,75 @@ export default function AdminUsersPage() {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.2, delay: index * 0.03 }}
|
||||
className="group hover:bg-muted/40 border-b border-border/50 transition-colors"
|
||||
className={`group border-b border-border/50 transition-colors ${user.isBlocked ? "bg-destructive/5 hover:bg-destructive/10" : "hover:bg-muted/40"}`}
|
||||
>
|
||||
<TableCell>
|
||||
<div className="font-mono text-xs text-muted-foreground/80">{user.telegramUserId}</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="font-medium flex items-center gap-2">
|
||||
{user.telegramUsername !== "Unknown" ? (
|
||||
<>
|
||||
<span className="text-blue-500/80">@</span>
|
||||
{user.telegramUsername}
|
||||
</>
|
||||
) : (
|
||||
<span className="text-muted-foreground italic">Unknown</span>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-xs">{user.telegramUserId}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium">{user.totalOrders}</span>
|
||||
{user.completedOrders > 0 && (
|
||||
<Badge variant="outline" className="text-[10px] h-5 px-1.5 bg-green-500/10 text-green-600 border-green-200 dark:border-green-900">
|
||||
{user.completedOrders} done
|
||||
</Badge>
|
||||
<span className="font-medium">@{user.telegramUsername || "Unknown"}</span>
|
||||
{user.isBlocked && (
|
||||
<Badge variant="destructive" className="h-5 px-1.5 text-[10px]">Blocked</Badge>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{user.totalOrders}</TableCell>
|
||||
<TableCell>{formatCurrency(user.totalSpent)}</TableCell>
|
||||
<TableCell>
|
||||
<span className="font-medium tabular-nums">{formatCurrency(user.totalSpent)}</span>
|
||||
<div className="flex flex-col gap-1 text-xs">
|
||||
<span className="text-emerald-500">{user.completedOrders} Completed</span>
|
||||
<span className="text-muted-foreground">{user.paidOrders - user.completedOrders} Pending</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{user.isBlocked ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Badge variant="destructive" className="items-center gap-1">
|
||||
<Ban className="h-3 w-3" />
|
||||
Blocked
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
{user.blockedReason && (
|
||||
<TooltipContent>
|
||||
<p className="max-w-xs">{user.blockedReason}</p>
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : user.totalOrders > 0 ? (
|
||||
<Badge variant="default" className="bg-green-600 hover:bg-green-700">Active</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary">No Orders</Badge>
|
||||
)}
|
||||
<TableCell className="text-xs text-muted-foreground">
|
||||
{user.firstOrderDate ? new Date(user.firstOrderDate).toLocaleDateString() : "-"}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
{user.firstOrderDate
|
||||
? new Date(user.firstOrderDate).toLocaleDateString()
|
||||
: '-'}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
{user.lastOrderDate
|
||||
? new Date(user.lastOrderDate).toLocaleDateString()
|
||||
: '-'}
|
||||
<TableCell className="text-xs text-muted-foreground">
|
||||
{user.lastOrderDate ? new Date(user.lastOrderDate).toLocaleDateString() : "-"}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
{!user.isBlocked ? (
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10">
|
||||
<Ban className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex justify-end gap-1">
|
||||
{user.isBlocked ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button size="sm" variant="outline" className="h-8 border-emerald-500/20 text-emerald-500 hover:bg-emerald-500/10 hover:text-emerald-400">
|
||||
<UserCheck className="h-4 w-4 mr-1" />
|
||||
Unblock
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Unblock this user</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-green-600 hover:bg-green-500/10">
|
||||
<UserCheck className="h-4 w-4" />
|
||||
</Button>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button size="sm" variant="outline" className="h-8 border-destructive/20 text-destructive hover:bg-destructive/10 hover:text-destructive">
|
||||
<Ban className="h-4 w-4 mr-1" />
|
||||
Block
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Block access to the store</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</motion.tr>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="h-32 text-center text-muted-foreground">
|
||||
<div className="flex flex-col items-center justify-center gap-2">
|
||||
<Users className="h-8 w-8 opacity-20" />
|
||||
<p>No users found</p>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</TableBody>
|
||||
|
||||
Reference in New Issue
Block a user