Skip to content
This repository was archived by the owner on Feb 12, 2026. It is now read-only.

Commit 014e743

Browse files
committed
✨ server-side: pagiantion & search query
1 parent 663f22e commit 014e743

File tree

6 files changed

+176
-102
lines changed

6 files changed

+176
-102
lines changed

package-lock.json

Lines changed: 28 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"dotenv": "^16.3.1",
4040
"ejs": "^3.1.9",
4141
"express": "^4.18.2",
42+
"express-paginate": "^1.0.2",
4243
"express-session": "^1.17.3",
4344
"ffmpeg": "^0.0.4",
4445
"ffmpeg-static": "^5.2.0",

public/stylesheets/style.css

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,42 @@ html {
205205
animation: spin89345 1s linear infinite;
206206
}
207207

208+
.current-page {
209+
background-color: #25d46e9c;
210+
border: 1px solid #aaaaaa10;
211+
}
212+
213+
.table-responsive {
214+
display: block;
215+
width: 100%;
216+
overflow-x: auto;
217+
-webkit-overflow-scrolling: touch;
218+
}
219+
220+
.table-responsive > .table {
221+
margin-bottom: 0;
222+
}
223+
224+
.table {
225+
border-spacing: 0 15px;
226+
}
227+
228+
i {
229+
font-size: 1rem !important;
230+
}
231+
232+
.table tr {
233+
border-radius: 20px;
234+
}
235+
236+
tr td:nth-child(n+5), tr th:nth-child(n+5) {
237+
border-radius: 0 .625rem .625rem 0;
238+
}
239+
240+
tr td:nth-child(1), tr th:nth-child(1) {
241+
border-radius: .625rem 0 0 .625rem;
242+
}
243+
208244
@keyframes spin89345 {
209245
0% {
210246
transform: rotate(0deg);

routes/dashboard.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require('dayjs/plugin/duration');
66
const router = express.Router();
77

88
const validator = require('validator');
9+
const url = require('url');
910

1011
const db = require('../database/manager');
1112
const checkAuth = require('../middlewares/checkAuth');
@@ -177,13 +178,38 @@ router.get('/server/:guildID/members', checkAuth, async (req, res) => {
177178
}
178179

179180
// Get the members from the server
180-
const members = server.members.cache.toJSON();
181+
let members = server.members.cache.toJSON();
182+
183+
// Get search query from query params
184+
let searchQuery = req.query.search || '';
185+
if (searchQuery) {
186+
// Convert search query and member names to lowercase for case-insensitive search
187+
searchQuery = searchQuery.toLowerCase();
188+
members = members.filter(member => member.user.username && member.user.username.toLowerCase().includes(searchQuery));
189+
}
190+
191+
// Set items per page
192+
const itemsPerPage = 65;
193+
194+
// Get current page from query params, default to 1
195+
const currentPage = Number(req.query.page) || 1;
196+
197+
// Calculate total pages
198+
const totalPages = Math.ceil(members.length / itemsPerPage);
199+
200+
// Get members for current page
201+
const membersOnPage = members.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);
181202

182203
res.render('dashboard/members.ejs', {
183204
bot: req.client,
184205
user: req.user || null,
185206
guild: server,
186-
members: members,
207+
members: membersOnPage,
208+
totalPages: totalPages,
209+
currentPage: currentPage,
210+
searchQuery: searchQuery,
211+
url: url,
212+
req: req,
187213
dayjs: dayjs,
188214
});
189215
});

views/dashboard/manage.ejs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
src="<%= guild.icon ? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}?size=512` : 'https://i.imgur.com/RO3uUxX.png' %>"
1212
/>
1313
</div>
14-
<h1 class="mt-9 text-white text-center font-bold text-2xl">Manage <span class="text-green-400"><%= guild.name %></span></h1>
15-
<div class="h-screen flex items-center justify-center -mt-56">
14+
<h1 class="mt-5 text-white text-center font-bold text-2xl">Manage <span class="text-green-400"><%= guild.name %></span></h1>
15+
<div class="h-screen flex items-center justify-center -mt-40">
1616
<div class="max-w-md w-full bg-[#aaaaaa10] border border-[#aaaaaa10] shadow-lg p-8 rounded-lg">
1717
<form role="form" action="/dashboard/server/<%= guild.id %>" method="POST">
1818
<div class="mb-2">

views/dashboard/members.ejs

Lines changed: 81 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -9,49 +9,43 @@
99

1010
<div class="flex items-center justify-center min-h-screen">
1111
<div class="col-span-12">
12-
<div class="flex items-center justify-center mb-14 text-sm text-gray-400">
13-
<a href="/dashboard/server/<%= guild.id %>" class="flex-no-shrink bg-green-500 hover:bg-green-700 px-5 ml-4 py-2 text-xs shadow-sm hover:shadow-lg font-medium tracking-wider text-white rounded-full transition ease-in duration-300"><i class="fa-regular fa-circle-left"></i> Go back</a>
14-
</div>
15-
<form action="" class="relative mx-auto w-max">
16-
<input type="text" id="searchInput"
17-
class="mb-5 ml-4 cursor-pointer relative z-10 h-12 w-12 rounded-full bg-transparent pl-12 outline-none focus:w-full focus:cursor-text hover:border-gray-400 focus:border-gray-400 focus:pl-16 focus:pr-4 text-white" />
18-
<svg xmlns="http://www.w3.org/2000/svg" class="mb-7 ml-4 absolute inset-y-0 my-auto h-8 w-12 border-r border-transparent stroke-gray-500 px-3.5 peer-focus:border-gray-400 peer-focus:stroke-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
19-
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
20-
</svg>
21-
</form>
12+
<form action="/dashboard/server/<%= guild.id %>/members?search=<%= searchQuery %>" method="GET" class="relative mx-auto w-max" >
13+
<input type="text" name="search" class="mb-5 ml-4 cursor-pointer z-10 h-12 w-12 rounded-full bg-transparent pl-12 focus:w-full focus:cursor-text focus:pl-16 focus:pr-4 text-white focus:bg-[#aaaaaa10] focus:border focus:border-[#aaaaaa10] focus:shadow-lg" />
14+
<i class="fa-solid fa-magnifying-glass text-gray-400 absolute z-20 top-4 left-8"></i>
15+
</form>
2216
<div class="overflow-auto lg:overflow-visible ">
23-
<div class="table-responsive">
24-
<table id="myTable" class="table text-gray-400 border-separate space-y-5 text-sm">
25-
<thead class="bg-[#aaaaaa10] border border-[#aaaaaa10] shadow-lg text-gray-500">
26-
<tr>
27-
<th class="p-3">Display Name</th>
28-
<th class="p-3 text-left">Username (or <span title="Legacy Username and Discriminator">Tag</span>)</th>
29-
<th class="p-3 text-left">ID</th>
30-
<th class="p-3 text-left">Joined</th>
31-
<th class="p-3 text-left">Roles</th>
32-
</tr>
33-
</thead>
34-
<tbody>
35-
<% for (let i = 0; i < members.length; i++) { %>
36-
<%
37-
const roles = members[i].roles.cache.map(r => r);
38-
const index = roles.findIndex(r => r === "@everyone");
39-
40-
roles.splice(index, 1);
41-
%>
42-
<tr class="bg-[#aaaaaa10] border border-[#aaaaaa10] shadow-lg">
43-
<td class="p-3">
44-
<div class="flex align-items-center">
45-
<% if (members[i].user.avatar) { %>
46-
<img class="rounded-full h-12 w-12 object-cover" src="https://cdn.discordapp.com/avatars/<%= members[i].user.id %>/<%= members[i].user.avatar %>" />
47-
<% } else { %>
48-
<img class="rounded-full h-12 w-12 object-cover" src="https://i.imgur.com/RO3uUxX.png" />
49-
<% } %>
17+
<div class="table-responsive">
18+
<table id="myTable" class="table text-gray-400 border-separate space-y-5 text-sm">
19+
<thead class="bg-[#aaaaaa10] border border-[#aaaaaa10] shadow-lg text-gray-500">
20+
<tr>
21+
<th class="p-3">Display Name</th>
22+
<th class="p-3 text-left">Username (or <span title="Legacy Username and Discriminator">Tag</span>)</th>
23+
<th class="p-3 text-left">ID</th>
24+
<th class="p-3 text-left">Joined</th>
25+
<th class="p-3 text-left">Roles</th>
26+
</tr>
27+
</thead>
28+
<tbody>
29+
<% for (let i = 0; i < members.length; i++) { %>
30+
<%
31+
const roles = members[i].roles.cache.map(r => r);
32+
const index = roles.findIndex(r => r === "@everyone");
33+
34+
roles.splice(index, 1);
35+
%>
36+
<tr class="bg-[#aaaaaa10] border border-[#aaaaaa10] shadow-lg">
37+
<td class="p-3">
38+
<div class="flex align-items-center">
39+
<% if (members[i].user.avatar) { %>
40+
<img class="rounded-full h-12 w-12 object-cover" src="https://cdn.discordapp.com/avatars/<%= members[i].user.id %>/<%= members[i].user.avatar %>" />
41+
<% } else { %>
42+
<img class="rounded-full h-12 w-12 object-cover" src="https://i.imgur.com/RO3uUxX.png" />
43+
<% } %>
5044
<div class="p-3">
5145
<strong class="text-white"><%= members[i].displayName %></strong>
52-
<% if (members[i].user.bot) { %>
53-
<img title="Discord Bot" width="28px" src="https://i.imgur.com/OKVwc6m.png">
54-
<% } %>
46+
<% if (members[i].user.bot) { %>
47+
<img title="Discord Bot" width="28px" src="https://i.imgur.com/OKVwc6m.png">
48+
<% } %>
5549
</div>
5650
</div>
5751
</td>
@@ -65,66 +59,56 @@
6559
<span title="<%= dayjs(members[i].joinedAt).format("dddd MMMM Do YYYY, h:mm a") %>"><%= dayjs(members[i].joinedAt).format("DD/MM/YYYY, HH:mm:ss") %></span>
6660
</td>
6761
<td class="p-3 ">
68-
<% if (roles.length === 0) { %>
69-
<p class="center inline-block select-none whitespace-nowrap rounded-lg bg-gray-950 py-2 px-3.5 align-baseline font-sans text-xs font-bold uppercase leading-none text-white">None</p>
70-
<% } else { %>
71-
<% for (let j = 0; j < roles.length; j++) { %>
72-
<span class="center inline-block select-none whitespace-nowrap rounded-lg bg-gray-950 py-2 px-3.5 align-baseline font-sans text-xs font-bold uppercase leading-none" style="color: <%= roles[j].hexColor === "#000000" ? "#ffffff" : roles[j].hexColor %>" title="<%= roles[j].id %>"><b><%= roles[j].name %></b></span>
73-
<% } %>
74-
<% } %>
75-
</td>
76-
</tr>
77-
<% } %>
78-
</tbody>
79-
</table>
80-
</div>
81-
</div>
82-
</div>
83-
</div>
84-
<style>
85-
86-
.table-responsive {
87-
display: block;
88-
width: 100%;
89-
overflow-x: auto;
90-
-webkit-overflow-scrolling: touch;
91-
}
92-
93-
.table-responsive > .table {
94-
margin-bottom: 0;
95-
}
96-
97-
.table {
98-
border-spacing: 0 15px;
99-
}
62+
<% if (roles.length === 0) { %>
63+
<p class="center inline-block select-none whitespace-nowrap rounded-lg bg-gray-950 py-2 px-3.5 align-baseline font-sans text-xs font-bold uppercase leading-none text-white">None</p>
64+
<% } else { %>
65+
<% for (let j = 0; j < roles.length; j++) { %>
66+
<span class="center inline-block select-none whitespace-nowrap rounded-lg bg-gray-950 py-2 px-3.5 align-baseline font-sans text-xs font-bold uppercase leading-none" style="color: <%= roles[j].hexColor === "#000000" ? "#ffffff" : roles[j].hexColor %>" title="<%= roles[j].id %>"><b><%= roles[j].name %></b></span>
67+
<% } %>
68+
<% } %>
69+
</td>
70+
</tr>
71+
<% } %>
72+
</tbody>
73+
</table>
74+
</div>
75+
<div class="justify-center flex space-x-2 text-gray-400">
76+
<% if (currentPage > 1) { %>
77+
<a href="/dashboard/server/<%= guild.id %>/members?page=<%= currentPage - 1 %>">
78+
<button class="flex items-center justify-center w-10 h-10 rounded-full focus:shadow-outline hover:text-gray-500 hover:bg-[#aaaaaa10] hover:border hover:border-[#aaaaaa10] hover:shadow-lg">
79+
<i class="fa-solid fa-chevron-left w-4 h-4 fill-current"></i>
80+
</button>
81+
</a>
82+
<% } %>
10083

101-
i {
102-
font-size: 1rem !important;
103-
}
84+
<%
85+
let startPage = Math.max(1, currentPage - 2);
86+
let endPage = Math.min(totalPages, startPage + 4);
87+
startPage = Math.max(1, endPage - 4);
10488
105-
.table tr {
106-
border-radius: 20px;
107-
}
89+
for (let i = startPage; i <= endPage; i++) {
90+
let pageUrl = url.parse(req.url, true);
91+
pageUrl.query.page = i;
92+
delete pageUrl.search;
93+
%>
10894
109-
tr td:nth-child(n+5),
110-
tr th:nth-child(n+5) {
111-
border-radius: 0 .625rem .625rem 0;
112-
}
95+
<a href="/dashboard/server/<%= guild.id %>/members?page=<%= i %>">
96+
<button class="<%= i === currentPage ? 'current-page' : '' %> w-10 h-10 rounded-full focus:shadow-outline hover:bg-[#aaaaaa10] hover:border hover:border-[#aaaaaa10] hover:shadow-lg">
97+
<%= i %>
98+
</button>
99+
</a>
100+
<% } %>
113101

114-
tr td:nth-child(1),
115-
tr th:nth-child(1) {
116-
border-radius: .625rem 0 0 .625rem;
117-
}
118-
</style>
119-
<script>
120-
$(document).ready(function(){
121-
$("#searchInput").on("keyup", function() {
122-
var value = $(this).val().toLowerCase();
123-
$("#myTable tr").filter(function() {
124-
$(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
125-
});
126-
});
127-
});
128-
</script>
102+
<% if (currentPage < totalPages) { %>
103+
<a href="/dashboard/server/<%= guild.id %>/members?page=<%= currentPage + 1 %>">
104+
<button class="flex items-center justify-center w-10 h-10 rounded-full focus:shadow-outline hover:text-gray-500 hover:bg-[#aaaaaa10] hover:border hover:border-[#aaaaaa10] hover:shadow-lg">
105+
<i class="fa-solid fa-chevron-right w-4 h-4 fill-current"></i>
106+
</button>
107+
</a>
108+
<% } %>
109+
</div>
110+
</div>
111+
</div>
112+
</div>
129113

130114
<%- include("../partials/footer") %>

0 commit comments

Comments
 (0)