performance improvments
This commit is contained in:
1 parent
912981e3f3
commit
02c9681aca
13 files changed
+805
-474
No files matched your search
@@ -1,75 +1,34 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { UserGrowthChart } from '@/components/charts/UserGrowthChart'
|
||||
import { MembershipDistributionChart } from '@/components/charts/MembershipDistributionChart'
|
||||
import { RevenueChart } from '@/components/charts/RevenueChart'
|
||||
import { Card, CardHeader, CardContent } from '@/components/ui/card'
|
||||
|
||||
interface ChartData {
|
||||
label: string
|
||||
value: number
|
||||
color?: string
|
||||
}
|
||||
import { UserGrowthChart } from "@/components/charts/UserGrowthChart";
|
||||
import { MembershipDistributionChart } from "@/components/charts/MembershipDistributionChart";
|
||||
import { RevenueChart } from "@/components/charts/RevenueChart";
|
||||
import { Card, CardHeader, CardContent } from "@/components/ui/card";
|
||||
import { useAnalytics } from "@/hooks/use-api";
|
||||
|
||||
export function AnalyticsDashboard() {
|
||||
const [userGrowthData, setUserGrowthData] = useState<ChartData[]>([])
|
||||
const [membershipData, setMembershipData] = useState<ChartData[]>([])
|
||||
const [revenueData, setRevenueData] = useState<ChartData[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { data: analytics, isLoading } = useAnalytics(6);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAnalyticsData()
|
||||
}, [])
|
||||
const userGrowthData = analytics?.userGrowth ?? [];
|
||||
const membershipData = analytics?.membershipDistribution ?? [];
|
||||
const revenueData = analytics?.revenue ?? [];
|
||||
|
||||
const fetchAnalyticsData = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
// Mock data for demonstration - replace with real API calls
|
||||
const mockUserGrowth = [
|
||||
{ label: 'Jan', value: 45 },
|
||||
{ label: 'Feb', value: 52 },
|
||||
{ label: 'Mar', value: 61 },
|
||||
{ label: 'Apr', value: 58 },
|
||||
{ label: 'May', value: 67 },
|
||||
{ label: 'Jun', value: 74 },
|
||||
]
|
||||
const totalUsers =
|
||||
userGrowthData.length > 0
|
||||
? userGrowthData[userGrowthData.length - 1].value
|
||||
: 0;
|
||||
const totalRevenue = revenueData.reduce((sum, item) => sum + item.value, 0);
|
||||
const activeMembers = membershipData.reduce(
|
||||
(sum, item) => sum + item.value,
|
||||
0,
|
||||
);
|
||||
|
||||
const mockMembershipData = [
|
||||
{ label: 'Basic', value: 45, color: '#6b7280' },
|
||||
{ label: 'Premium', value: 28, color: '#3b82f6' },
|
||||
{ label: 'VIP', value: 12, color: '#f59e0b' },
|
||||
]
|
||||
|
||||
const mockRevenueData = [
|
||||
{ label: 'Jan', value: 12500, color: '#10b981' },
|
||||
{ label: 'Feb', value: 14200, color: '#10b981' },
|
||||
{ label: 'Mar', value: 16800, color: '#10b981' },
|
||||
{ label: 'Apr', value: 15900, color: '#10b981' },
|
||||
{ label: 'May', value: 18200, color: '#10b981' },
|
||||
{ label: 'Jun', value: 19400, color: '#10b981' },
|
||||
]
|
||||
|
||||
setUserGrowthData(mockUserGrowth)
|
||||
setMembershipData(mockMembershipData)
|
||||
setRevenueData(mockRevenueData)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch analytics data:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const totalUsers = userGrowthData.length > 0 ? userGrowthData[userGrowthData.length - 1].value : 0
|
||||
const totalRevenue = revenueData.reduce((sum, item) => sum + item.value, 0)
|
||||
const activeMembers = membershipData.reduce((sum, item) => sum + item.value, 0)
|
||||
|
||||
if (loading) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="text-lg">Loading analytics...</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -81,7 +40,9 @@ export function AnalyticsDashboard() {
|
||||
<Card>
|
||||
<CardContent>
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-blue-600">{totalUsers}</div>
|
||||
<div className="text-3xl font-bold text-blue-600">
|
||||
{totalUsers}
|
||||
</div>
|
||||
<div className="text-gray-600">Total Users</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -90,7 +51,9 @@ export function AnalyticsDashboard() {
|
||||
<Card>
|
||||
<CardContent>
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-green-600">${totalRevenue.toLocaleString()}</div>
|
||||
<div className="text-3xl font-bold text-green-600">
|
||||
${totalRevenue.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-gray-600">Total Revenue</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -99,7 +62,9 @@ export function AnalyticsDashboard() {
|
||||
<Card>
|
||||
<CardContent>
|
||||
<div className="text-center">
|
||||
<div className="text-3xl font-bold text-purple-600">{activeMembers}</div>
|
||||
<div className="text-3xl font-bold text-purple-600">
|
||||
{activeMembers}
|
||||
</div>
|
||||
<div className="text-gray-600">Active Members</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -133,14 +98,14 @@ export function AnalyticsDashboard() {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<RevenueChart
|
||||
data={revenueData.map(item => ({
|
||||
data={revenueData.map((item) => ({
|
||||
category: item.label,
|
||||
value: item.value,
|
||||
color: item.color
|
||||
color: item.color,
|
||||
}))}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { useState, type ReactNode } from "react";
|
||||
|
||||
interface QueryProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function QueryProvider({ children }: QueryProviderProps) {
|
||||
const [queryClient] = useState(
|
||||
() =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 60 * 1000,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
@@ -13,40 +13,18 @@ import {
|
||||
Brain,
|
||||
} from "lucide-react";
|
||||
import { UserButton, useUser } from "@clerk/nextjs";
|
||||
|
||||
interface Recommendation {
|
||||
id: string;
|
||||
status: string;
|
||||
}
|
||||
import { usePendingRecommendationsCount } from "@/hooks/use-api";
|
||||
|
||||
export function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const { user } = useUser();
|
||||
const [pendingCount, setPendingCount] = useState(0);
|
||||
const { data: pendingCount = 0 } = usePendingRecommendationsCount();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPending = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/recommendations");
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
const pending = (data.recommendations || []).filter(
|
||||
(r: Recommendation) => r.status === "pending",
|
||||
);
|
||||
setPendingCount(pending.length);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch pending recommendations", e);
|
||||
}
|
||||
};
|
||||
fetchPending();
|
||||
}, []);
|
||||
|
||||
const menuItems = [
|
||||
{ icon: LayoutDashboard, label: "Dashboard", href: "/" },
|
||||
{ icon: Users, label: "Users", href: "/users" },
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SkeletonProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Skeleton({ className }: SkeletonProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"animate-pulse rounded-md bg-slate-200 dark:bg-slate-700",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function StatsCardSkeleton() {
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Skeleton className="h-12 w-12 rounded-full" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<Skeleton className="h-4 w-20" />
|
||||
<Skeleton className="h-8 w-16" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableSkeleton({ rows = 5 }: { rows?: number }) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{Array.from({ length: rows }).map((_, i) => (
|
||||
<div key={i} className="flex gap-4">
|
||||
<Skeleton className="h-12 flex-1" />
|
||||
<Skeleton className="h-12 w-32" />
|
||||
<Skeleton className="h-12 w-24" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardSkeleton() {
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-6 space-y-4">
|
||||
<Skeleton className="h-6 w-40" />
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-full" />
|
||||
<Skeleton className="h-4 w-3/4" />
|
||||
<Skeleton className="h-4 w-5/6" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -9,9 +9,10 @@ import { formatDate } from "@/lib/utils";
|
||||
|
||||
ModuleRegistry.registerModules([AllCommunityModule]);
|
||||
|
||||
function getTimeAgo(date: Date): string {
|
||||
function getTimeAgo(date: Date | string): string {
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const d = typeof date === "string" ? new Date(date) : date;
|
||||
const diffMs = now.getTime() - d.getTime();
|
||||
const diffMins = Math.floor(diffMs / 60000);
|
||||
const diffHours = Math.floor(diffMins / 60);
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
@@ -22,7 +23,7 @@ function getTimeAgo(date: Date): string {
|
||||
return `${diffDays}d ago`;
|
||||
}
|
||||
|
||||
interface User {
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
@@ -31,15 +32,18 @@ interface User {
|
||||
phone?: string;
|
||||
gymId?: string;
|
||||
gymName?: string | null;
|
||||
createdAt: Date;
|
||||
createdAt?: string | Date;
|
||||
isCheckedIn?: boolean;
|
||||
checkInTime?: Date;
|
||||
checkInTime?: string | Date;
|
||||
lastCheckInTime?: string | Date;
|
||||
checkInsThisWeek?: number;
|
||||
checkInsThisMonth?: number;
|
||||
client?: {
|
||||
id: string;
|
||||
membershipType: string;
|
||||
membershipStatus: string;
|
||||
joinDate: Date;
|
||||
lastVisit?: Date;
|
||||
joinDate: string | Date;
|
||||
lastVisit?: string | Date;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { UserGrid } from "@/components/users/UserGrid";
|
||||
// import { Button } from "@/components/ui/button";
|
||||
import { useState } from "react";
|
||||
import { UserGrid, type User } from "@/components/users/UserGrid";
|
||||
import { Card, CardHeader, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useUser } from "@clerk/nextjs";
|
||||
import { getGymIdFromUser } from "@/lib/error-helpers";
|
||||
import log from "@/lib/logger";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { CreateUserModal } from "./CreateUserModal";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: string;
|
||||
phone?: string;
|
||||
gymId?: string;
|
||||
createdAt: Date;
|
||||
isCheckedIn?: boolean;
|
||||
checkInTime?: Date;
|
||||
lastCheckInTime?: Date;
|
||||
checkInsThisWeek?: number;
|
||||
checkInsThisMonth?: number;
|
||||
client?: {
|
||||
id: string;
|
||||
membershipType: string;
|
||||
membershipStatus: string;
|
||||
joinDate: Date;
|
||||
lastVisit?: Date;
|
||||
};
|
||||
}
|
||||
import {
|
||||
useUsers,
|
||||
useGyms,
|
||||
useUpdateUser,
|
||||
useDeleteUser,
|
||||
useSendInvitation,
|
||||
} from "@/hooks/use-api";
|
||||
|
||||
export function UserManagement() {
|
||||
const { user } = useUser();
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [filter, setFilter] = useState<string>("all");
|
||||
const [selectedUser, setSelectedUser] = useState<User | null>(null);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
@@ -52,68 +32,17 @@ export function UserManagement() {
|
||||
gymId: string;
|
||||
} | null>(null);
|
||||
|
||||
// Active gyms for dropdown
|
||||
const [gyms, setGyms] = useState<Array<{ id: string; name: string }>>([]);
|
||||
|
||||
// Load gyms when modal opens or refreshes
|
||||
useEffect(() => {
|
||||
if (isEditing) {
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch("/api/gyms");
|
||||
const data = await res.json();
|
||||
if (Array.isArray(data)) {
|
||||
// map down to id and name to avoid extra payload use here
|
||||
setGyms(data.map((g: any) => ({ id: g.id, name: g.name })));
|
||||
} else {
|
||||
setGyms([]);
|
||||
}
|
||||
} catch {
|
||||
setGyms([]);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}, [isEditing]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUsers();
|
||||
}, [filter]);
|
||||
|
||||
const fetchUsers = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const ts = Date.now();
|
||||
const url =
|
||||
filter === "all"
|
||||
? `/api/users?ts=${ts}`
|
||||
: `/api/users?role=${filter}&ts=${ts}`;
|
||||
|
||||
log.debug("Fetching users", { url });
|
||||
const response = await fetch(url, { cache: "no-store" });
|
||||
log.debug("Users fetch response", {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
});
|
||||
const responseData = await response.json();
|
||||
const data = responseData.data || responseData; // Handle both old and new API response formats
|
||||
log.debug("Received users data", {
|
||||
count: Array.isArray(data.users) ? data.users.length : 0,
|
||||
sample:
|
||||
data.users && data.users[0]
|
||||
? {
|
||||
id: data.users[0].id,
|
||||
gymId: data.users[0].gymId,
|
||||
role: data.users[0].role,
|
||||
}
|
||||
: null,
|
||||
});
|
||||
setUsers(data.users || []);
|
||||
} catch (error) {
|
||||
log.error("Failed to fetch users", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const {
|
||||
data: users = [],
|
||||
isLoading,
|
||||
refetch,
|
||||
} = useUsers({
|
||||
role: filter !== "all" ? filter : undefined,
|
||||
});
|
||||
const { data: gyms = [] } = useGyms();
|
||||
const updateUser = useUpdateUser();
|
||||
const deleteUser = useDeleteUser();
|
||||
const sendInvitation = useSendInvitation();
|
||||
|
||||
const handleUserSelect = (user: User | null) => {
|
||||
setSelectedUser(user);
|
||||
@@ -144,17 +73,12 @@ export function UserManagement() {
|
||||
return;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/users", {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ ids: users.map((u) => u.id) }),
|
||||
});
|
||||
if (response.ok) {
|
||||
fetchUsers();
|
||||
toast.success("Users deleted successfully");
|
||||
} else {
|
||||
toast.error("Error deleting users");
|
||||
}
|
||||
const deletePromises = users.map((u) =>
|
||||
fetch(`/api/users?id=${u.id}`, { method: "DELETE" }),
|
||||
);
|
||||
await Promise.all(deletePromises);
|
||||
refetch();
|
||||
toast.success("Users deleted successfully");
|
||||
} catch (error) {
|
||||
log.error("Failed to delete users", error);
|
||||
}
|
||||
@@ -196,7 +120,7 @@ export function UserManagement() {
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
fetchUsers();
|
||||
refetch();
|
||||
};
|
||||
|
||||
const handleSaveEdit = async () => {
|
||||
@@ -204,7 +128,6 @@ export function UserManagement() {
|
||||
|
||||
try {
|
||||
if (selectedUser) {
|
||||
// Update existing user
|
||||
const payload = {
|
||||
id: selectedUser.id,
|
||||
email: editForm.email,
|
||||
@@ -214,80 +137,21 @@ export function UserManagement() {
|
||||
phone: editForm.phone,
|
||||
gymId: editForm.gymId === "" ? null : editForm.gymId,
|
||||
};
|
||||
log.debug("Updating user", payload);
|
||||
const response = await fetch("/api/users", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
log.debug("User update response", {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
});
|
||||
if (response.ok) {
|
||||
// Optimistically update local state so grid reflects changes immediately
|
||||
setUsers((prev) =>
|
||||
prev.map((u) =>
|
||||
u.id === selectedUser.id
|
||||
? {
|
||||
...u,
|
||||
email: editForm.email,
|
||||
firstName: editForm.firstName,
|
||||
lastName: editForm.lastName,
|
||||
role: editForm.role,
|
||||
phone: editForm.phone || undefined,
|
||||
gymId: editForm.gymId === "" ? undefined : editForm.gymId,
|
||||
}
|
||||
: u,
|
||||
),
|
||||
);
|
||||
setSelectedUser((prev) =>
|
||||
prev
|
||||
? {
|
||||
...prev,
|
||||
email: editForm.email,
|
||||
firstName: editForm.firstName,
|
||||
lastName: editForm.lastName,
|
||||
role: editForm.role,
|
||||
phone: editForm.phone || undefined,
|
||||
gymId: editForm.gymId === "" ? undefined : editForm.gymId,
|
||||
}
|
||||
: prev,
|
||||
);
|
||||
setIsEditing(false);
|
||||
setEditForm(null);
|
||||
// Still re-fetch from server to ensure consistency
|
||||
log.debug("Re-fetching users after successful edit");
|
||||
fetchUsers();
|
||||
toast.success("User updated successfully");
|
||||
} else {
|
||||
const errText = await response.text().catch(() => "");
|
||||
log.error("User update failed", new Error(errText), {
|
||||
status: response.status,
|
||||
});
|
||||
toast.error("Error updating user");
|
||||
}
|
||||
} else {
|
||||
// Create (Invite) new user
|
||||
const response = await fetch("/api/invitations", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
inviteeEmail: editForm.email,
|
||||
roleAssigned: editForm.role,
|
||||
gymId: editForm.gymId || undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
setIsEditing(false);
|
||||
setEditForm(null);
|
||||
fetchUsers();
|
||||
toast.success("Invitation sent successfully!");
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
toast.error(`Error sending invitation: ${errorData.error}`);
|
||||
}
|
||||
await updateUser.mutateAsync(payload);
|
||||
setIsEditing(false);
|
||||
setEditForm(null);
|
||||
refetch();
|
||||
toast.success("User updated successfully");
|
||||
} else {
|
||||
await sendInvitation.mutateAsync({
|
||||
email: editForm.email,
|
||||
role: editForm.role,
|
||||
});
|
||||
setIsEditing(false);
|
||||
setEditForm(null);
|
||||
refetch();
|
||||
toast.success("Invitation sent successfully!");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -299,17 +163,11 @@ export function UserManagement() {
|
||||
if (!selectedUser) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/users?id=${selectedUser.id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (response.ok) {
|
||||
setIsDeleting(false);
|
||||
setSelectedUser(null);
|
||||
fetchUsers();
|
||||
toast.success("User deleted successfully");
|
||||
} else {
|
||||
toast.error("Error deleting user");
|
||||
}
|
||||
await deleteUser.mutateAsync(selectedUser.id);
|
||||
setIsDeleting(false);
|
||||
setSelectedUser(null);
|
||||
refetch();
|
||||
toast.success("User deleted successfully");
|
||||
} catch (error) {
|
||||
log.error("Failed to delete user", error);
|
||||
}
|
||||
@@ -397,7 +255,7 @@ export function UserManagement() {
|
||||
onEditUser={handleEditUser}
|
||||
onDeleteUser={handleDeleteUser}
|
||||
onBulkDelete={handleBulkDelete}
|
||||
loading={loading}
|
||||
loading={isLoading}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -591,7 +449,9 @@ export function UserManagement() {
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-medium">Joined:</span>{" "}
|
||||
{new Date(selectedUser.createdAt).toLocaleDateString()}
|
||||
{selectedUser.createdAt
|
||||
? new Date(selectedUser.createdAt).toLocaleDateString()
|
||||
: "N/A"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -653,7 +513,7 @@ export function UserManagement() {
|
||||
<CreateUserModal
|
||||
open={createModalOpen}
|
||||
onOpenChange={setCreateModalOpen}
|
||||
onSuccess={() => fetchUsers()}
|
||||
onSuccess={() => refetch()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user