From 0cb6e260f7b71535fa1a092473977d26a71d5ddd Mon Sep 17 00:00:00 2001 From: MOHAN Date: Fri, 12 Jun 2026 14:52:46 +0530 Subject: [PATCH] feat: admin panel shows all app users with inline grant/revoke free access - New GET /d4a-admin/api/users endpoint merges tokens.json (all installed shops) with freeAccessStore to show every user and their access status - Dashboard replaced with a full user list: shop domain, last auth date, free-access status badge, and Grant Free / Revoke buttons per row - Grant opens a modal to set optional expiry date and note (no manual typing) - Search filter to find shops quickly across large user lists - Removed the manual text-input add form Co-Authored-By: Claude Sonnet 4.6 --- routes/adminPanel.js | 213 ++++++++++++++++++++++++++----------------- 1 file changed, 127 insertions(+), 86 deletions(-) diff --git a/routes/adminPanel.js b/routes/adminPanel.js index 1bcf9eb..64c848b 100644 --- a/routes/adminPanel.js +++ b/routes/adminPanel.js @@ -2,6 +2,7 @@ const express = require('express'); const crypto = require('crypto'); const { isShopAllowed, addShop, removeShop, listShops } = require('../freeAccessStore'); +const { listTokens } = require('../tokenStore'); const router = express.Router(); @@ -79,6 +80,25 @@ router.delete('/api/shops/:shop', requireAuth, (req, res) => { res.json({ ok: true }); }); +// ── all installed users (from tokens.json) merged with free-access status ──── +router.get('/api/users', requireAuth, (_req, res) => { + const tokens = listTokens(); + const freeList = listShops(); + const freeMap = Object.fromEntries(freeList.map(s => [s.shop, s])); + const users = Object.entries(tokens).map(([shop, t]) => { + const fa = freeMap[shop] || null; + return { + shop, + savedAt: t.savedAt || null, + hasToken: !!t.accessToken, + freeAccess: !!fa && !fa.expired, + freeEntry: fa || null, + }; + }); + users.sort((a, b) => (b.savedAt || '').localeCompare(a.savedAt || '')); + res.json({ users }); +}); + // ── serve admin HTML (always — login gate is client-side) ──────────────────── router.get('/', (_req, res) => { res.setHeader('Content-Type', 'text/html; charset=utf-8'); @@ -353,6 +373,13 @@ function adminHtml() { cursor:pointer;font-family:inherit;transition:background 0.12s; } .btn-del:hover{background:#fee2e2;} + .btn-grant{ + background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px; + color:#2563eb;padding:5px 12px;font-size:12px;font-weight:600; + cursor:pointer;font-family:inherit;transition:background 0.12s; + } + .btn-grant:hover{background:#dbeafe;} + .note-cell{max-width:180px;} .empty-state{padding:56px 24px;text-align:center;} .empty-state .icon{font-size:38px;margin-bottom:12px;} .empty-state p{font-size:14px;color:#94a3b8;font-weight:500;} @@ -436,50 +463,45 @@ function adminHtml() {
- 0 shops + 0 users
- -
-
-
-
-

Grant Free Access

-
Add a shop domain and optional expiry date.
-
-
-
-
- - -
-
+ +