Improve user search and optimize Next.js config
User search in the admin users page now queries the backend instead of filtering on the client, and resets pagination on search. Next.js config adds production compression, removes console logs in production (except error/warn), disables the poweredByHeader, and introduces custom webpack code splitting for better bundle optimization. Updated tsconfig target to ES2020.
This commit is contained in:
@@ -55,12 +55,19 @@ export default function AdminUsersPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUsers();
|
fetchUsers();
|
||||||
}, [page]);
|
}, [page, searchQuery]);
|
||||||
|
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const data = await fetchClient<PaginationResponse>(`/admin/users?page=${page}&limit=25`);
|
const params = new URLSearchParams({
|
||||||
|
page: page.toString(),
|
||||||
|
limit: '25'
|
||||||
|
});
|
||||||
|
if (searchQuery.trim()) {
|
||||||
|
params.append('search', searchQuery.trim());
|
||||||
|
}
|
||||||
|
const data = await fetchClient<PaginationResponse>(`/admin/users?${params.toString()}`);
|
||||||
setUsers(data.users);
|
setUsers(data.users);
|
||||||
setPagination(data.pagination);
|
setPagination(data.pagination);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -75,15 +82,6 @@ export default function AdminUsersPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredUsers = users.filter((user) => {
|
|
||||||
if (!searchQuery) return true;
|
|
||||||
const query = searchQuery.toLowerCase();
|
|
||||||
return (
|
|
||||||
user.telegramUserId.toLowerCase().includes(query) ||
|
|
||||||
user.telegramUsername.toLowerCase().includes(query)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const usersWithOrders = users.filter(u => u.totalOrders > 0);
|
const usersWithOrders = users.filter(u => u.totalOrders > 0);
|
||||||
const returningCustomers = users.filter(u => u.totalOrders > 1);
|
const returningCustomers = users.filter(u => u.totalOrders > 1);
|
||||||
const blockedUsers = users.filter(u => u.isBlocked);
|
const blockedUsers = users.filter(u => u.isBlocked);
|
||||||
@@ -208,7 +206,10 @@ export default function AdminUsersPage() {
|
|||||||
placeholder="Search users..."
|
placeholder="Search users..."
|
||||||
className="pl-8 w-64"
|
className="pl-8 w-64"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setSearchQuery(e.target.value);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -219,7 +220,7 @@ export default function AdminUsersPage() {
|
|||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
) : filteredUsers.length === 0 ? (
|
) : users.length === 0 ? (
|
||||||
<div className="text-center py-8 text-muted-foreground">
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
{searchQuery ? "No users found matching your search" : "No users found"}
|
{searchQuery ? "No users found matching your search" : "No users found"}
|
||||||
</div>
|
</div>
|
||||||
@@ -238,7 +239,7 @@ export default function AdminUsersPage() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{filteredUsers.map((user) => (
|
{users.map((user) => (
|
||||||
<TableRow key={user.telegramUserId}>
|
<TableRow key={user.telegramUserId}>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="font-mono text-sm">{user.telegramUserId}</div>
|
<div className="font-mono text-sm">{user.telegramUserId}</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const withBundleAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === 'tr
|
|||||||
const baseConfig = {
|
const baseConfig = {
|
||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
reactStrictMode: false,
|
reactStrictMode: false,
|
||||||
|
compress: process.env.NODE_ENV === 'production',
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
@@ -16,7 +17,6 @@ const baseConfig = {
|
|||||||
protocol: "https",
|
protocol: "https",
|
||||||
hostname: "telegram.org",
|
hostname: "telegram.org",
|
||||||
},
|
},
|
||||||
// Backend API hostname configured via environment variable
|
|
||||||
...(process.env.API_HOSTNAME ? [{
|
...(process.env.API_HOSTNAME ? [{
|
||||||
protocol: "https",
|
protocol: "https",
|
||||||
hostname: process.env.API_HOSTNAME,
|
hostname: process.env.API_HOSTNAME,
|
||||||
@@ -26,7 +26,6 @@ const baseConfig = {
|
|||||||
async rewrites() {
|
async rewrites() {
|
||||||
const apiBaseUrl = process.env.API_BASE_URL;
|
const apiBaseUrl = process.env.API_BASE_URL;
|
||||||
|
|
||||||
// Ensure API_BASE_URL is valid to prevent 500 errors
|
|
||||||
if (!apiBaseUrl || apiBaseUrl === 'undefined') {
|
if (!apiBaseUrl || apiBaseUrl === 'undefined') {
|
||||||
console.warn('⚠️ API_BASE_URL not set! Set it to your backend domain');
|
console.warn('⚠️ API_BASE_URL not set! Set it to your backend domain');
|
||||||
console.warn('⚠️ Using localhost fallback - this will fail in production!');
|
console.warn('⚠️ Using localhost fallback - this will fail in production!');
|
||||||
@@ -46,8 +45,64 @@ const baseConfig = {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
experimental: {
|
compiler: {
|
||||||
// serverExternalPackages has been deprecated in Next.js 15
|
removeConsole: process.env.NODE_ENV === 'production' ? {
|
||||||
|
exclude: ['error', 'warn'],
|
||||||
|
} : false,
|
||||||
|
},
|
||||||
|
poweredByHeader: false,
|
||||||
|
webpack: (config, { isServer, dev }) => {
|
||||||
|
if (!isServer && !dev) {
|
||||||
|
// Only apply aggressive code splitting in production
|
||||||
|
config.optimization = {
|
||||||
|
...config.optimization,
|
||||||
|
moduleIds: 'deterministic',
|
||||||
|
splitChunks: {
|
||||||
|
chunks: 'all',
|
||||||
|
cacheGroups: {
|
||||||
|
default: false,
|
||||||
|
vendors: false,
|
||||||
|
recharts: {
|
||||||
|
test: /[\\/]node_modules[\\/]recharts[\\/]/,
|
||||||
|
name: 'recharts',
|
||||||
|
chunks: 'all',
|
||||||
|
priority: 30,
|
||||||
|
},
|
||||||
|
radix: {
|
||||||
|
test: /[\\/]node_modules[\\/]@radix-ui[\\/]/,
|
||||||
|
name: 'radix-ui',
|
||||||
|
chunks: 'all',
|
||||||
|
priority: 25,
|
||||||
|
},
|
||||||
|
reactMarkdown: {
|
||||||
|
test: /[\\/]node_modules[\\/]react-markdown[\\/]/,
|
||||||
|
name: 'react-markdown',
|
||||||
|
chunks: 'all',
|
||||||
|
priority: 20,
|
||||||
|
},
|
||||||
|
dateFns: {
|
||||||
|
test: /[\\/]node_modules[\\/]date-fns[\\/]/,
|
||||||
|
name: 'date-fns',
|
||||||
|
chunks: 'all',
|
||||||
|
priority: 15,
|
||||||
|
},
|
||||||
|
framework: {
|
||||||
|
name: 'framework',
|
||||||
|
chunks: 'all',
|
||||||
|
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
|
||||||
|
priority: 40,
|
||||||
|
enforce: true,
|
||||||
|
},
|
||||||
|
commons: {
|
||||||
|
name: 'commons',
|
||||||
|
minChunks: 2,
|
||||||
|
priority: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return config;
|
||||||
},
|
},
|
||||||
onDemandEntries: {
|
onDemandEntries: {
|
||||||
maxInactiveAge: 15 * 1000,
|
maxInactiveAge: 15 * 1000,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"commitHash": "0062aa2",
|
"commitHash": "5f1e294",
|
||||||
"buildTime": "2025-12-31T05:39:04.712Z"
|
"buildTime": "2025-12-31T06:44:25.736Z"
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"./*"
|
"./*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"target": "ES2017"
|
"target": "ES2020"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
|
|||||||
Reference in New Issue
Block a user