Skip to content

Commit cf57b7b

Browse files
committed
fix: address issues from PR #9 code review
Made-with: Cursor
1 parent f42c2cd commit cf57b7b

5 files changed

Lines changed: 50 additions & 38 deletions

File tree

public/css/style.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2195,6 +2195,12 @@ html {
21952195
background: var(--accent);
21962196
}
21972197

2198+
.radio-group {
2199+
display: flex;
2200+
gap: 16px;
2201+
flex-wrap: wrap;
2202+
}
2203+
21982204
.form-group .datetime-input {
21992205
color-scheme: dark;
22002206
padding-right: 44px;

src/routes/panel.js

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -847,24 +847,14 @@ router.post('/users/:userId', requireAuth, async (req, res) => {
847847
const parsedExpireAt = new Date(expireAtRaw);
848848

849849
if (Number.isNaN(parsedExpireAt.getTime())) {
850+
draftUser.expireAt = null;
850851
return render(res, 'user-form', {
851-
title: `Редактирование ${req.params.userId}`,
852+
title: res.locals.t('users.editUser') + ' ' + req.params.userId,
852853
page: 'users',
853854
groups: availableGroups,
854855
user: draftUser,
855856
isEdit: true,
856-
error: 'Некорректный формат даты/времени окончания',
857-
});
858-
}
859-
860-
if (parsedExpireAt.getTime() < Date.now()) {
861-
return render(res, 'user-form', {
862-
title: `Редактирование ${req.params.userId}`,
863-
page: 'users',
864-
groups: availableGroups,
865-
user: draftUser,
866-
isEdit: true,
867-
error: 'Дата/время окончания не может быть в прошлом',
857+
error: res.locals.t('users.expireAtInvalidError'),
868858
});
869859
}
870860

@@ -896,6 +886,8 @@ router.post('/users/:userId', requireAuth, async (req, res) => {
896886
await cache.clearDeviceIPs(req.params.userId);
897887
await cache.invalidateDashboardCounts();
898888

889+
webhookService.emit(webhookService.EVENTS.USER_UPDATED, { userId: req.params.userId, updates });
890+
899891
res.redirect(`/panel/users/${req.params.userId}`);
900892
} catch (error) {
901893
res.status(500).send('Ошибка: ' + error.message);

views/user-detail.ejs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,16 @@
103103
</div>
104104
<div class="card-body">
105105
<div class="actions-list">
106-
<% if (user.enabled) { %>
107-
<button class="btn btn-warning btn-block" onclick="toggleUser(false)">
108-
<%= t('users.disableUser') %>
109-
</button>
110-
<% } else { %>
111-
<button class="btn btn-success btn-block" onclick="toggleUser(true)">
112-
<%= t('users.enableUser') %>
113-
</button>
106+
<% if (!isExpired) { %>
107+
<% if (user.enabled) { %>
108+
<button class="btn btn-warning btn-block" onclick="toggleUser(false)">
109+
<%= t('users.disableUser') %>
110+
</button>
111+
<% } else { %>
112+
<button class="btn btn-success btn-block" onclick="toggleUser(true)">
113+
<%= t('users.enableUser') %>
114+
</button>
115+
<% } %>
114116
<% } %>
115117
116118
<a href="/panel/users/<%= user.userId %>/edit" class="btn btn-primary btn-block">

views/user-form.ejs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ const selectedGroupIds = new Set(
77
const trafficLimitGB = isEditMode ? Math.round((user.trafficLimit || 0) / (1024 * 1024 * 1024)) : 0;
88
const maxDevicesValue = isEditMode ? (parseInt(user.maxDevices, 10) || 0) : 0;
99
const hasExpireAt = isEditMode && user.expireAt;
10-
const expireAtDate = hasExpireAt ? new Date(user.expireAt) : null;
11-
const expireAtLocalValue = expireAtDate && !Number.isNaN(expireAtDate.getTime())
12-
? new Date(expireAtDate.getTime() - (expireAtDate.getTimezoneOffset() * 60000)).toISOString().slice(0, 16)
13-
: '';
10+
const expireAtIso = hasExpireAt ? new Date(user.expireAt).toISOString() : '';
1411
%>
1512
<div class="page-header">
1613
<a href="/panel/users" class="btn btn-back"><%= t('common.backToList') %></a>
@@ -93,11 +90,11 @@ const expireAtLocalValue = expireAtDate && !Number.isNaN(expireAtDate.getTime())
9390

9491
<div class="form-group" id="expireDatetimeGroup" style="display: <%= hasExpireAt ? 'block' : 'none' %>;">
9592
<label for="expireAtLocal"><%= t('users.expireAtLabel') %></label>
96-
<input type="datetime-local" id="expireAtLocal" name="expireAtLocal" class="datetime-input" value="<%= expireAtLocalValue %>">
93+
<input type="datetime-local" id="expireAtLocal" name="expireAtLocal" class="datetime-input" data-utc="<%= expireAtIso %>">
9794
<small><%= t('users.expireAtHint') %></small>
9895
</div>
9996

100-
<input type="hidden" id="expireAt" name="expireAt" value="<%= hasExpireAt ? new Date(user.expireAt).toISOString() : '' %>">
97+
<input type="hidden" id="expireAt" name="expireAt" value="<%= expireAtIso %>">
10198

10299
<div class="form-group">
103100
<label class="checkbox-label">
@@ -123,14 +120,28 @@ const expireAtLocalValue = expireAtDate && !Number.isNaN(expireAtDate.getTime())
123120
const expireAtInput = document.getElementById('expireAt');
124121
const form = document.getElementById('userForm');
125122
126-
function updateExpireMode() {
123+
function utcToLocal(isoString) {
124+
if (!isoString) return '';
125+
const d = new Date(isoString);
126+
if (Number.isNaN(d.getTime())) return '';
127+
const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);
128+
return local.toISOString().slice(0, 16);
129+
}
130+
131+
function initExpireAtLocal() {
132+
const utc = expireAtLocalInput.dataset.utc;
133+
if (utc) {
134+
expireAtLocalInput.value = utcToLocal(utc);
135+
}
136+
}
137+
138+
function updateExpireMode(skipInit) {
127139
const selectedMode = document.querySelector('input[name="expireMode"]:checked').value;
128140
129141
if (selectedMode === 'datetime') {
130142
expireDaysGroup.style.display = 'none';
131143
expireDatetimeGroup.style.display = 'block';
132144
expireDaysInput.value = '';
133-
syncExpireAt();
134145
} else {
135146
expireDaysGroup.style.display = 'block';
136147
expireDatetimeGroup.style.display = 'none';
@@ -155,7 +166,7 @@ const expireAtLocalValue = expireAtDate && !Number.isNaN(expireAtDate.getTime())
155166
}
156167
157168
expireModeInputs.forEach((input) => {
158-
input.addEventListener('change', updateExpireMode);
169+
input.addEventListener('change', () => updateExpireMode());
159170
});
160171
161172
expireAtLocalInput.addEventListener('change', syncExpireAt);
@@ -174,6 +185,7 @@ const expireAtLocalValue = expireAtDate && !Number.isNaN(expireAtDate.getTime())
174185
syncExpireAt();
175186
});
176187
188+
initExpireAtLocal();
177189
updateExpireMode();
178190
})();
179191
</script>

views/users.ejs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -255,15 +255,15 @@ function getSortIcon(field) {
255255
%>
256256
257257
<script>
258-
const baseUrl = <%- JSON.stringify(baseUrl) %>;
259-
const currentSortBy = <%- JSON.stringify(query.sortBy || "createdAt") %>;
260-
const currentSortOrder = <%- JSON.stringify(query.sortOrder || "desc") %>;
258+
const baseUrl = <%= JSON.stringify(baseUrl) %>;
259+
const currentSortBy = <%= JSON.stringify(String(query.sortBy || "createdAt").replace(/[<>]/g, "")) %>;
260+
const currentSortOrder = <%= JSON.stringify(String(query.sortOrder || "desc").replace(/[<>]/g, "")) %>;
261261
const i18n = {
262-
turnedOn: <%- JSON.stringify(t("users.turnedOn")) %>,
263-
turnedOff: <%- JSON.stringify(t("users.turnedOff")) %>,
264-
error: <%- JSON.stringify(t("common.error")) %>,
265-
linkCopied: <%- JSON.stringify(t("users.linkCopied")) %>,
266-
copyError: <%- JSON.stringify(t("users.copyError")) %>
262+
turnedOn: <%= JSON.stringify(t("users.turnedOn")) %>,
263+
turnedOff: <%= JSON.stringify(t("users.turnedOff")) %>,
264+
error: <%= JSON.stringify(t("common.error")) %>,
265+
linkCopied: <%= JSON.stringify(t("users.linkCopied")) %>,
266+
copyError: <%= JSON.stringify(t("users.copyError")) %>
267267
};
268268
269269
// Debounced search

0 commit comments

Comments
 (0)