diff --git a/public/css/style.css b/public/css/style.css
index 2dd81c4..36a4883 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -648,6 +648,45 @@ tr:hover td {
line-height: 1.5;
}
+/* Filter Bar */
+.filter-bar {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.filter-label {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--text-muted);
+ margin-right: 4px;
+}
+
+.filter-btn {
+ background: var(--input-bg);
+ border: 1px solid var(--card-border);
+ color: var(--text-muted);
+ padding: 10px 20px;
+ border-radius: 10px;
+ cursor: pointer;
+ font-family: 'Montserrat', sans-serif;
+ font-size: 13px;
+ font-weight: 600;
+ transition: all 0.2s;
+}
+
+.filter-btn:hover {
+ border-color: rgba(255, 196, 7, 0.3);
+ color: var(--text-main);
+}
+
+.filter-btn.active {
+ background: rgba(255, 196, 7, 0.1);
+ border-color: var(--accent-primary);
+ color: var(--accent-primary);
+}
+
/* Responsive */
@media (max-width: 768px) {
.sidebar {
diff --git a/public/index.html b/public/index.html
index 843ab00..24fe058 100644
--- a/public/index.html
+++ b/public/index.html
@@ -29,6 +29,10 @@
Find User
+
Bulk Operations
@@ -69,7 +73,7 @@
Bulk Operations
Add tokens to all users at once or reset everyone to a specific balance amount.
-
+
+
+
+
+
+ Show users under:
+
+
+
+
+
+
+
+
+
+
+
+ | Email |
+ Name |
+ Balance |
+ Actions |
+
+
+
+
+
+
diff --git a/public/js/app.js b/public/js/app.js
index c79665a..ba8fe27 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -80,6 +80,7 @@ const app = {
if (view === 'home') this.loadHome();
if (view === 'all') this.loadAll();
+ if (view === 'low') this.loadLow(1000000);
if (view === 'search') document.getElementById('search-input').focus();
},
@@ -132,6 +133,21 @@ const app = {
this.renderPagination(data);
},
+ async loadLow(threshold) {
+ document.querySelectorAll('.filter-btn').forEach(btn => {
+ btn.classList.toggle('active', parseInt(btn.dataset.threshold) === threshold);
+ });
+ const data = await this.api(`/balances/low?threshold=${threshold}`);
+ if (!data) return;
+ const tbody = document.getElementById('low-tbody');
+ document.getElementById('low-count').textContent = `${data.users.length} User${data.users.length !== 1 ? 's' : ''} under ${(threshold / 1000000)}M`;
+ if (data.users.length === 0) {
+ tbody.innerHTML = '| No users below this threshold |
';
+ } else {
+ tbody.innerHTML = data.users.map(u => this.userRow(u)).join('');
+ }
+ },
+
userRow(u) {
const credits = u.tokenCredits || 0;
const balanceClass = credits > 0 ? 'balance-positive' : 'balance-zero';
diff --git a/routes/api.js b/routes/api.js
index 305c233..6454521 100644
--- a/routes/api.js
+++ b/routes/api.js
@@ -22,6 +22,16 @@ router.get('/balances', async (req, res) => {
}
});
+router.get('/balances/low', async (req, res) => {
+ try {
+ const { threshold = 1000000 } = req.query;
+ const users = await balanceService.getLowBalances(req.db, parseInt(threshold));
+ res.json({ users });
+ } catch (err) {
+ res.status(500).json({ error: err.message });
+ }
+});
+
router.get('/balances/search', async (req, res) => {
try {
const { q } = req.query;
diff --git a/services/balance.js b/services/balance.js
index 21bb99a..aaefe78 100644
--- a/services/balance.js
+++ b/services/balance.js
@@ -82,6 +82,31 @@ async function getAllBalances(db, page, limit) {
};
}
+async function getLowBalances(db, threshold) {
+ return db.collection('balances').aggregate([
+ { $match: { tokenCredits: { $lt: threshold } } },
+ {
+ $lookup: {
+ from: 'users',
+ localField: 'user',
+ foreignField: '_id',
+ as: 'userInfo',
+ },
+ },
+ { $unwind: { path: '$userInfo', preserveNullAndEmptyArrays: true } },
+ {
+ $project: {
+ userId: '$user',
+ tokenCredits: 1,
+ email: '$userInfo.email',
+ name: '$userInfo.name',
+ username: '$userInfo.username',
+ },
+ },
+ { $sort: { tokenCredits: 1 } },
+ ]).toArray();
+}
+
async function searchUsers(db, query) {
const regex = new RegExp(query, 'i');
@@ -181,6 +206,7 @@ async function setAll(db, amount) {
module.exports = {
getStats,
getAllBalances,
+ getLowBalances,
searchUsers,
getUserBalance,
setBalance,