sequelize-game-bank 5.0.0 → 5.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/js/js.js +771 -526
- package/public/login.html +129 -66
- package/public/register.html +505 -431
package/public/js/js.js
CHANGED
|
@@ -1,620 +1,865 @@
|
|
|
1
|
-
const toastifyScript = document.createElement(
|
|
2
|
-
toastifyScript.src =
|
|
1
|
+
const toastifyScript = document.createElement("script");
|
|
2
|
+
toastifyScript.src = "https://cdn.jsdelivr.net/npm/toastify-js";
|
|
3
3
|
document.head.append(toastifyScript);
|
|
4
4
|
let currentUsername = "";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
});
|
|
66
|
-
window.location.href = '/bank/login.html';
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
let result = await response.json();
|
|
70
|
-
let mannyRes = await fetch('/api/bank/users/my-total-manny');
|
|
71
|
-
let mannyData = await mannyRes.json();
|
|
72
|
-
currentUsername = mannyData.username;
|
|
73
|
-
const userDisplay = document.getElementById('user-display');
|
|
74
|
-
if (userDisplay) userDisplay.innerText = currentUsername.toUpperCase();
|
|
75
|
-
const userDisplayGeneral = document.getElementById('user-display-page');
|
|
76
|
-
if (userDisplayGeneral) userDisplayGeneral.innerText = currentUsername.toUpperCase();
|
|
77
|
-
const totalManny = document.getElementById('total-manny');
|
|
78
|
-
if (totalManny) totalManny.innerText = mannyData.totalManny.toFixed(2);
|
|
79
|
-
const adminNavButton = document.getElementById('admin-nav');
|
|
80
|
-
if (adminNavButton) {
|
|
81
|
-
if (currentUsername === 'root') {
|
|
82
|
-
Toastify({
|
|
83
|
-
text: "Здраствуйте админестратор!",
|
|
84
|
-
duration: 3000,
|
|
85
|
-
close: true,
|
|
86
|
-
gravity: "top",
|
|
87
|
-
position: "right",
|
|
88
|
-
stopOnFocus: true,
|
|
89
|
-
style: {
|
|
90
|
-
background: "linear-gradient(to right, green, blue)",
|
|
91
|
-
}
|
|
92
|
-
}).showToast();
|
|
93
|
-
adminNavButton.style.display = 'block';
|
|
94
|
-
} else {
|
|
95
|
-
Toastify({
|
|
96
|
-
text: `Здраствуйте, ${currentUsername}`,
|
|
97
|
-
duration: 3000,
|
|
98
|
-
close: true,
|
|
99
|
-
gravity: "top",
|
|
100
|
-
position: "right",
|
|
101
|
-
stopOnFocus: true,
|
|
102
|
-
style: {
|
|
103
|
-
background: "linear-gradient(to right, green, blue)",
|
|
104
|
-
}
|
|
105
|
-
}).showToast();
|
|
106
|
-
adminNavButton.style.display = 'none';
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
const tbody = document.querySelector('#table-accounts tbody');
|
|
110
|
-
const selectFrom = document.getElementById('tx-from');
|
|
111
|
-
const selectCredit = document.getElementById('credit-target');
|
|
112
|
-
if (tbody) tbody.innerHTML = "";
|
|
113
|
-
if (selectFrom) selectFrom.innerHTML = "";
|
|
114
|
-
if (selectCredit) selectCredit.innerHTML = "";
|
|
115
|
-
if (result.accounts && Array.isArray(result.accounts)) {
|
|
116
|
-
result.accounts.forEach(acc => {
|
|
117
|
-
if (tbody) {
|
|
118
|
-
tbody.innerHTML += `
|
|
119
|
-
<tr>
|
|
120
|
-
<td>#${acc.id}</td>
|
|
121
|
-
<td><b>${parseFloat(acc.manny).toFixed(2)}</b> руб.</td>
|
|
122
|
-
<td>${acc.two_fa_status ? '🔒 Активна' : '❌ Выкл'}</td>
|
|
123
|
-
<td>${acc.active ? '<span style="color:green">Активен</span>' : '<span style="color:red">Забанен</span>'}</td>
|
|
124
|
-
<td><button class="btn btn-danger" onclick="closeAccount(${acc.id})">Закрыть</button></td>
|
|
125
|
-
</tr>`;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (acc.active) {
|
|
129
|
-
let opt = `<option value="${acc.id}">Счет #${acc.id} (${parseFloat(acc.manny).toFixed(2)} руб.)</option>`;
|
|
130
|
-
if (selectFrom) selectFrom.innerHTML += opt;
|
|
131
|
-
if (selectCredit) selectCredit.innerHTML += opt;
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
let profileRes = await fetch('/api/bank/users/profile');
|
|
136
|
-
let profileData = await profileRes.json();
|
|
137
|
-
const userAvatar = document.getElementById('user-avatar');
|
|
138
|
-
window.tippy("#user-avatar", {
|
|
139
|
-
content:`<span style='color:blue'>${currentUsername.toLocaleUpperCase()}</span> — <span style='color:lightgreen;'>${mannyData.totalManny.toFixed(2)}</span>`,
|
|
140
|
-
placement:"right",
|
|
141
|
-
allowHTML:true,
|
|
142
|
-
theme:'user-info',
|
|
143
|
-
arrow:false
|
|
144
|
-
})
|
|
145
|
-
const userAvatar2 = document.getElementById('user-avatar2');
|
|
146
|
-
if (profileData.status && profileData.user && profileData.user.avatar && userAvatar) {
|
|
147
|
-
userAvatar.src = profileData.user.avatar;
|
|
148
|
-
}
|
|
149
|
-
if (profileData.status && profileData.user && profileData.user.avatar && userAvatar2) {
|
|
150
|
-
userAvatar2.src = profileData.user.avatar;
|
|
151
|
-
}
|
|
152
|
-
await syncProfileSectionData();
|
|
153
|
-
if (document.getElementById('table-logs')) {
|
|
154
|
-
await loadTransactionLogs();
|
|
155
|
-
}
|
|
156
|
-
} catch(e) {
|
|
157
|
-
console.error("Критическая ошибка выполнения скрипта страницы:", e);
|
|
5
|
+
function showTab(tabId) {
|
|
6
|
+
document
|
|
7
|
+
.querySelectorAll(".section")
|
|
8
|
+
.forEach((s) => s.classList.remove("active"));
|
|
9
|
+
document
|
|
10
|
+
.querySelectorAll(".menu-btn")
|
|
11
|
+
.forEach((b) => b.classList.remove("active"));
|
|
12
|
+
document.getElementById(tabId).classList.add("active");
|
|
13
|
+
event.target.classList.add("active");
|
|
14
|
+
}
|
|
15
|
+
window.tippy("#general", {
|
|
16
|
+
content: "Главная",
|
|
17
|
+
placement: "top-end",
|
|
18
|
+
arrow: false,
|
|
19
|
+
animation: "scale-extreme",
|
|
20
|
+
theme: "light",
|
|
21
|
+
});
|
|
22
|
+
window.tippy("#acccount", {
|
|
23
|
+
content: "Щета",
|
|
24
|
+
placement: "top-end",
|
|
25
|
+
arrow: false,
|
|
26
|
+
animation: "scale-extreme",
|
|
27
|
+
theme: "light",
|
|
28
|
+
});
|
|
29
|
+
window.tippy("#transfer", {
|
|
30
|
+
content: "Перевод",
|
|
31
|
+
placement: "top-end",
|
|
32
|
+
arrow: false,
|
|
33
|
+
animation: "scale-extreme",
|
|
34
|
+
theme: "light",
|
|
35
|
+
});
|
|
36
|
+
window.tippy("#history", {
|
|
37
|
+
content: "История",
|
|
38
|
+
placement: "top-end",
|
|
39
|
+
arrow: false,
|
|
40
|
+
animation: "scale-extreme",
|
|
41
|
+
theme: "light",
|
|
42
|
+
});
|
|
43
|
+
window.tippy("#credit", {
|
|
44
|
+
content: "Кредит",
|
|
45
|
+
placement: "top-end",
|
|
46
|
+
arrow: false,
|
|
47
|
+
animation: "scale-extreme",
|
|
48
|
+
theme: "light",
|
|
49
|
+
});
|
|
50
|
+
window.tippy("#profile", {
|
|
51
|
+
content: "Редактировать профиль",
|
|
52
|
+
placement: "top-end",
|
|
53
|
+
arrow: false,
|
|
54
|
+
animation: "scale-extreme",
|
|
55
|
+
theme: "light",
|
|
56
|
+
});
|
|
57
|
+
async function loadData() {
|
|
58
|
+
try {
|
|
59
|
+
let response = await fetch("/api/bank/accounts/my", {
|
|
60
|
+
headers: {
|
|
61
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
if (response.status === 401 || response.status === 403) {
|
|
158
65
|
window.Swal.fire({
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
66
|
+
title: "Внимание!",
|
|
67
|
+
text: "Сессия устарела!",
|
|
68
|
+
icon: "warning",
|
|
69
|
+
iconColor: "orange",
|
|
70
|
+
confirmButtonText: "ОК",
|
|
71
|
+
confirmButtonColor: "#3085d6",
|
|
72
|
+
background: "#d6cb3089",
|
|
165
73
|
});
|
|
74
|
+
window.location.href = "/bank/login.html";
|
|
75
|
+
return;
|
|
166
76
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const res = await fetch('/api/bank/accounts', {
|
|
173
|
-
method: 'POST',
|
|
174
|
-
headers: {'Content-Type': 'application/json'},
|
|
175
|
-
body: JSON.stringify({ pinCode: pin.value, enableAccount2FA: fa.isConfirmed })
|
|
77
|
+
let result = await response.json();
|
|
78
|
+
let mannyRes = await fetch("/api/bank/users/my-total-manny", {
|
|
79
|
+
headers: {
|
|
80
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
81
|
+
},
|
|
176
82
|
});
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
83
|
+
let mannyData = await mannyRes.json();
|
|
84
|
+
currentUsername = mannyData.username;
|
|
85
|
+
const userDisplay = document.getElementById("user-display");
|
|
86
|
+
if (userDisplay) userDisplay.innerText = currentUsername.toUpperCase();
|
|
87
|
+
const userDisplayGeneral = document.getElementById("user-display-page");
|
|
88
|
+
if (userDisplayGeneral)
|
|
89
|
+
userDisplayGeneral.innerText = currentUsername.toUpperCase();
|
|
90
|
+
const totalManny = document.getElementById("total-manny");
|
|
91
|
+
if (totalManny) totalManny.innerText = mannyData.totalManny.toFixed(2);
|
|
92
|
+
const adminNavButton = document.getElementById("admin-nav");
|
|
93
|
+
if (adminNavButton) {
|
|
94
|
+
if (currentUsername === "root") {
|
|
95
|
+
Toastify({
|
|
96
|
+
text: "Здраствуйте админестратор!",
|
|
182
97
|
duration: 3000,
|
|
183
98
|
close: true,
|
|
184
99
|
gravity: "top",
|
|
185
100
|
position: "right",
|
|
186
101
|
stopOnFocus: true,
|
|
187
102
|
style: {
|
|
188
|
-
background: "linear-gradient(to right,
|
|
189
|
-
}
|
|
103
|
+
background: "linear-gradient(to right, green, blue)",
|
|
104
|
+
},
|
|
190
105
|
}).showToast();
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
106
|
+
adminNavButton.style.display = "block";
|
|
107
|
+
} else {
|
|
108
|
+
Toastify({
|
|
109
|
+
text: `Здраствуйте, ${currentUsername}`,
|
|
110
|
+
duration: 3000,
|
|
111
|
+
close: true,
|
|
112
|
+
gravity: "top",
|
|
113
|
+
position: "right",
|
|
114
|
+
stopOnFocus: true,
|
|
115
|
+
style: {
|
|
116
|
+
background: "linear-gradient(to right, green, blue)",
|
|
117
|
+
},
|
|
118
|
+
}).showToast();
|
|
119
|
+
adminNavButton.style.display = "none";
|
|
120
|
+
}
|
|
199
121
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
122
|
+
const tbody = document.querySelector("#table-accounts tbody");
|
|
123
|
+
const selectFrom = document.getElementById("tx-from");
|
|
124
|
+
const selectCredit = document.getElementById("credit-target");
|
|
125
|
+
if (tbody) tbody.innerHTML = "";
|
|
126
|
+
if (selectFrom) selectFrom.innerHTML = "";
|
|
127
|
+
if (selectCredit) selectCredit.innerHTML = "";
|
|
128
|
+
if (result.accounts && Array.isArray(result.accounts)) {
|
|
129
|
+
result.accounts.forEach((acc) => {
|
|
130
|
+
if (tbody) {
|
|
131
|
+
tbody.innerHTML += `
|
|
132
|
+
<tr>
|
|
133
|
+
<td>#${acc.id}</td>
|
|
134
|
+
<td><b>${parseFloat(acc.manny).toFixed(2)}</b> руб.</td>
|
|
135
|
+
<td>${acc.two_fa_status ? "🔒 Активна" : "❌ Выкл"}</td>
|
|
136
|
+
<td>${acc.active ? '<span style="color:green">Активен</span>' : '<span style="color:red">Забанен</span>'}</td>
|
|
137
|
+
<td><button class="btn btn-danger" onclick="closeAccount(${acc.id})">Закрыть</button></td>
|
|
138
|
+
</tr>`;
|
|
139
|
+
}
|
|
208
140
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
141
|
+
if (acc.active) {
|
|
142
|
+
let opt = `<option value="${acc.id}">Счет #${acc.id} (${parseFloat(acc.manny).toFixed(2)} руб.)</option>`;
|
|
143
|
+
if (selectFrom) selectFrom.innerHTML += opt;
|
|
144
|
+
if (selectCredit) selectCredit.innerHTML += opt;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
let profileRes = await fetch("/api/bank/users/profile", {
|
|
149
|
+
headers: {
|
|
150
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
151
|
+
},
|
|
213
152
|
});
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
153
|
+
let profileData = await profileRes.json();
|
|
154
|
+
const userAvatar = document.getElementById("user-avatar");
|
|
155
|
+
window.tippy("#user-avatar", {
|
|
156
|
+
content: `<span style='color:blue'>${currentUsername.toLocaleUpperCase()}</span> — <span style='color:lightgreen;'>${mannyData.totalManny.toFixed(2)}</span>`,
|
|
157
|
+
placement: "right",
|
|
158
|
+
allowHTML: true,
|
|
159
|
+
theme: "user-info",
|
|
160
|
+
arrow: false,
|
|
161
|
+
});
|
|
162
|
+
const userAvatar2 = document.getElementById("user-avatar2");
|
|
163
|
+
if (
|
|
164
|
+
profileData.status &&
|
|
165
|
+
profileData.user &&
|
|
166
|
+
profileData.user.avatar &&
|
|
167
|
+
userAvatar
|
|
168
|
+
) {
|
|
169
|
+
userAvatar.src = profileData.user.avatar;
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
profileData.status &&
|
|
173
|
+
profileData.user &&
|
|
174
|
+
profileData.user.avatar &&
|
|
175
|
+
userAvatar2
|
|
176
|
+
) {
|
|
177
|
+
userAvatar2.src = profileData.user.avatar;
|
|
229
178
|
}
|
|
179
|
+
await syncProfileSectionData();
|
|
180
|
+
if (document.getElementById("table-logs")) {
|
|
181
|
+
await loadTransactionLogs();
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.error("Критическая ошибка выполнения скрипта страницы:", e);
|
|
185
|
+
window.Swal.fire({
|
|
186
|
+
title: "Внимание!",
|
|
187
|
+
text: `Ошибка кода!${e}`,
|
|
188
|
+
icon: "error",
|
|
189
|
+
confirmButtonText: "ОК",
|
|
190
|
+
confirmButtonColor: "#d63030",
|
|
191
|
+
background: "#d63030",
|
|
192
|
+
});
|
|
230
193
|
}
|
|
194
|
+
}
|
|
195
|
+
async function openCreateAccountModal() {
|
|
196
|
+
const pin = await window.Swal.fire({
|
|
197
|
+
title: "Создание щёта",
|
|
198
|
+
text: "Введите надёжный пин-код!",
|
|
199
|
+
icon: "question",
|
|
200
|
+
iconColor: "blue",
|
|
201
|
+
input: "number",
|
|
202
|
+
inputAttributes: { autocapitalize: "off" },
|
|
203
|
+
preConfirm: async (pincode) => {
|
|
204
|
+
return pincode;
|
|
205
|
+
},
|
|
206
|
+
confirmButtonText: "ОК",
|
|
207
|
+
confirmButtonColor: "#3085d6",
|
|
208
|
+
background: "#ffffffaf",
|
|
209
|
+
});
|
|
210
|
+
if (!pin.value) return;
|
|
211
|
+
const fa = await Swal.fire({
|
|
212
|
+
title: "Создание щёта",
|
|
213
|
+
text: "Включить индивидуальную 2FA-защиту транзакций для этого счета?",
|
|
214
|
+
icon: "question",
|
|
215
|
+
iconColor: "blue",
|
|
216
|
+
showCancelButton: true,
|
|
217
|
+
confirmButtonColor: "#3085d6",
|
|
218
|
+
cancelButtonColor: "#3085d6",
|
|
219
|
+
confirmButtonText: "Да, включить",
|
|
220
|
+
cancelButtonText: "Нет, не включать",
|
|
221
|
+
}).then((result) => {
|
|
222
|
+
return result;
|
|
223
|
+
});
|
|
224
|
+
const res = await fetch("/api/bank/accounts", {
|
|
225
|
+
method: "POST",
|
|
226
|
+
headers: {
|
|
227
|
+
"Content-Type": "application/json",
|
|
228
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify({
|
|
231
|
+
pinCode: pin.value,
|
|
232
|
+
enableAccount2FA: fa.isConfirmed,
|
|
233
|
+
}),
|
|
234
|
+
});
|
|
235
|
+
const data = await res.json();
|
|
236
|
+
if (data.status) {
|
|
237
|
+
loadData();
|
|
238
|
+
Toastify({
|
|
239
|
+
text: `Вы создали щёт! \n Проверте "Мои щета"`,
|
|
240
|
+
duration: 3000,
|
|
241
|
+
close: true,
|
|
242
|
+
gravity: "top",
|
|
243
|
+
position: "right",
|
|
244
|
+
stopOnFocus: true,
|
|
245
|
+
style: {
|
|
246
|
+
background: "linear-gradient(to right, yellowgreen, green)",
|
|
247
|
+
},
|
|
248
|
+
}).showToast();
|
|
249
|
+
} else {
|
|
250
|
+
Toastify({
|
|
251
|
+
text: `cоздание щёта не удалось!`,
|
|
252
|
+
duration: 3000,
|
|
253
|
+
close: true,
|
|
254
|
+
style: { background: "RGB(245, 158, 11)" },
|
|
255
|
+
}).showToast();
|
|
256
|
+
console.error("Ошибка: " + data.message);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function executeTransfer() {
|
|
260
|
+
const fromId = document.getElementById("tx-from").value;
|
|
261
|
+
const toId = document.getElementById("tx-to").value.trim();
|
|
262
|
+
const amount = document.getElementById("tx-amount").value;
|
|
263
|
+
const pinCode = document.getElementById("tx-pin").value;
|
|
264
|
+
const fa = await Swal.fire({
|
|
265
|
+
title: "Перевод",
|
|
266
|
+
text: "Вы подтверждаете перевод?",
|
|
267
|
+
icon: "question",
|
|
268
|
+
iconColor: "blue",
|
|
269
|
+
showCancelButton: true,
|
|
270
|
+
confirmButtonColor: "#3085d6",
|
|
271
|
+
cancelButtonColor: "#3085d6",
|
|
272
|
+
confirmButtonText: "Да, перевести",
|
|
273
|
+
cancelButtonText: "Нет, не переводить",
|
|
274
|
+
}).then((result) => {
|
|
275
|
+
return result;
|
|
276
|
+
});
|
|
277
|
+
if (!fa.isConfirmed) return;
|
|
278
|
+
|
|
279
|
+
const res = await fetch("/api/bank/accounts/transfer", {
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers: {
|
|
282
|
+
"Content-Type": "application/json",
|
|
283
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
284
|
+
},
|
|
285
|
+
body: JSON.stringify({ fromId, toId, amount, pinCode }),
|
|
286
|
+
});
|
|
287
|
+
const data = await res.json();
|
|
288
|
+
if (data.status) {
|
|
289
|
+
Toastify({
|
|
290
|
+
text: `Вы перевели ${amount} на другой щёт\n #${fromId} → #${toId}`,
|
|
291
|
+
duration: 3000,
|
|
292
|
+
style: { background: "rgb(58, 245, 11)" },
|
|
293
|
+
}).showToast();
|
|
294
|
+
loadData();
|
|
295
|
+
} else {
|
|
296
|
+
Toastify({
|
|
297
|
+
text: `Перевод средств не удался\n #${fromId} → #${toId}`,
|
|
298
|
+
duration: 3000,
|
|
299
|
+
style: { background: "RGB(245, 158, 11)" },
|
|
300
|
+
}).showToast();
|
|
301
|
+
console.error("Ошибка [" + data.statusCode + "]: " + data.message);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
231
304
|
async function applyForCredit() {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
305
|
+
const targetAccountId = document.getElementById("credit-target").value;
|
|
306
|
+
const amount = document.getElementById("credit-amount").value;
|
|
307
|
+
const paymentsCount = document.getElementById("credit-payments").value;
|
|
308
|
+
const autoWithdrawal = document.getElementById("credit-auto").checked;
|
|
309
|
+
const pinCode = document.getElementById("credit-pin").value;
|
|
310
|
+
const res = await fetch("/api/bank/accounts/credit", {
|
|
311
|
+
method: "POST",
|
|
312
|
+
headers: {
|
|
313
|
+
"Content-Type": "application/json",
|
|
314
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
315
|
+
},
|
|
316
|
+
body: JSON.stringify({
|
|
317
|
+
accountId: targetAccountId,
|
|
318
|
+
amount: parseFloat(amount),
|
|
319
|
+
termMonths: parseInt(paymentsCount, 10),
|
|
320
|
+
autoRepay: autoWithdrawal,
|
|
321
|
+
pinCode: pinCode,
|
|
322
|
+
rate: 0.1,
|
|
323
|
+
}),
|
|
324
|
+
});
|
|
325
|
+
const data = await res.json();
|
|
326
|
+
if (data.success || data.status) {
|
|
327
|
+
await Swal.fire({
|
|
328
|
+
title: "Кредит",
|
|
329
|
+
text:
|
|
330
|
+
"Кредит одобрен! Деньги зачислены на счет #" +
|
|
331
|
+
(data.creditId || data.creditAccountId),
|
|
332
|
+
icon: "success",
|
|
333
|
+
confirmButtonColor: "#3085d6",
|
|
334
|
+
confirmButtonText: "Ок",
|
|
248
335
|
});
|
|
249
|
-
const data = await res.json();
|
|
250
|
-
if(data.success || data.status) {
|
|
251
|
-
await Swal.fire({
|
|
252
|
-
title: 'Кредит',
|
|
253
|
-
text: "Кредит одобрен! Деньги зачислены на счет #" + (data.creditId || data.creditAccountId),
|
|
254
|
-
icon: 'success',
|
|
255
|
-
confirmButtonColor: '#3085d6',
|
|
256
|
-
confirmButtonText: 'Ок'
|
|
257
|
-
});
|
|
258
336
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
337
|
+
Toastify({
|
|
338
|
+
text: `Вы взяли кредит`,
|
|
339
|
+
duration: 3000,
|
|
340
|
+
style: { background: "rgb(58, 245, 11)" },
|
|
341
|
+
}).showToast();
|
|
342
|
+
|
|
343
|
+
if (typeof loadData === "function") loadData();
|
|
344
|
+
} else {
|
|
345
|
+
await Swal.fire({
|
|
346
|
+
title: "Кредит",
|
|
347
|
+
text: data.error || data.message || "Ошибка оформления кредита",
|
|
348
|
+
icon: "warning",
|
|
349
|
+
iconColor: "orange",
|
|
350
|
+
confirmButtonColor: "#3085d6",
|
|
351
|
+
confirmButtonText: "Ок",
|
|
352
|
+
});
|
|
353
|
+
console.error("Ошибка: " + (data.error || data.message));
|
|
354
|
+
}
|
|
277
355
|
}
|
|
278
356
|
document.getElementById("logoutBtn").addEventListener("click", async () => {
|
|
279
357
|
const logOut = await Swal.fire({
|
|
280
|
-
title:
|
|
281
|
-
text:
|
|
282
|
-
icon:
|
|
283
|
-
iconColor: "orange",
|
|
284
|
-
showCancelButton: true,
|
|
285
|
-
confirmButtonColor:
|
|
286
|
-
cancelButtonColor:
|
|
287
|
-
confirmButtonText:
|
|
288
|
-
cancelButtonText:
|
|
289
|
-
}).then((result) => {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
358
|
+
title: "Выход",
|
|
359
|
+
text: "Вы подтверждаете выход из аккаунта?",
|
|
360
|
+
icon: "warning",
|
|
361
|
+
iconColor: "orange",
|
|
362
|
+
showCancelButton: true,
|
|
363
|
+
confirmButtonColor: "#d63030",
|
|
364
|
+
cancelButtonColor: "#46d630",
|
|
365
|
+
confirmButtonText: "Да, выйти",
|
|
366
|
+
cancelButtonText: "Отмена",
|
|
367
|
+
}).then((result) => {
|
|
368
|
+
return result;
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
if (!logOut.isConfirmed) return;
|
|
372
|
+
|
|
373
|
+
await Toastify({
|
|
374
|
+
text: `Выход из аккаунта`,
|
|
375
|
+
duration: 3000,
|
|
376
|
+
style: { background: "rgb(245, 128, 11)" },
|
|
377
|
+
}).showToast();
|
|
378
|
+
|
|
295
379
|
try {
|
|
296
|
-
await fetch(
|
|
380
|
+
await fetch("/api/bank/users/logout", {
|
|
381
|
+
method: "POST",
|
|
382
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
383
|
+
});
|
|
297
384
|
} catch (e) {
|
|
298
|
-
console.error(
|
|
385
|
+
console.error("Ошибка сети при логауте:", e.message);
|
|
299
386
|
}
|
|
300
|
-
window.location.href =
|
|
387
|
+
window.location.href = "/bank/home.html";
|
|
301
388
|
});
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
389
|
+
window.onload = loadData;
|
|
390
|
+
let user2FaActive = false;
|
|
391
|
+
async function syncProfileSectionData() {
|
|
392
|
+
try {
|
|
393
|
+
const response = await fetch("/api/bank/users/profile/settings", {
|
|
394
|
+
headers: {
|
|
395
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
if (!response.ok) {
|
|
399
|
+
console.warn(
|
|
400
|
+
"⚠️ [City-bank] Не удалось загрузить данные профиля, сервер вернул статус:",
|
|
401
|
+
response.status,
|
|
402
|
+
);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const result = await response.json();
|
|
406
|
+
if (result.status && result.user) {
|
|
407
|
+
if (result.user.avatar) {
|
|
408
|
+
document.getElementById("user-avatar").src = result.user.avatar;
|
|
310
409
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
statusText.innerHTML = 'Текущий статус: <span style="color:red; font-weight:bold;">НЕБЕЗОПАСНО (2FA выключена)</span>';
|
|
326
|
-
toggleBtn.innerText = 'Включить защиту 2FA';
|
|
327
|
-
toggleBtn.className = 'btn';
|
|
328
|
-
}
|
|
410
|
+
user2FaActive = result.user.two_fa_status;
|
|
411
|
+
const statusText = document.getElementById("2fa-current-status");
|
|
412
|
+
const toggleBtn = document.getElementById("btn-toggle-2fa");
|
|
413
|
+
|
|
414
|
+
if (user2FaActive) {
|
|
415
|
+
statusText.innerHTML =
|
|
416
|
+
'Текущий статус: <span style="color:green; font-weight:bold;">ЗАЩИЩЕН (2FA включена)</span>';
|
|
417
|
+
toggleBtn.innerText = "Отключить защиту 2FA";
|
|
418
|
+
toggleBtn.className = "btn btn-danger";
|
|
419
|
+
} else {
|
|
420
|
+
statusText.innerHTML =
|
|
421
|
+
'Текущий статус: <span style="color:red; font-weight:bold;">НЕБЕЗОПАСНО (2FA выключена)</span>';
|
|
422
|
+
toggleBtn.innerText = "Включить защиту 2FA";
|
|
423
|
+
toggleBtn.className = "btn";
|
|
329
424
|
}
|
|
330
|
-
} catch (e) {
|
|
331
|
-
console.error("Ошибка парсинга или запроса профиля:", e);
|
|
332
425
|
}
|
|
426
|
+
} catch (e) {
|
|
427
|
+
console.error("Ошибка парсинга или запроса профиля:", e);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async function uploadAvatar() {
|
|
431
|
+
const fileInput = document.getElementById("avatar-file");
|
|
432
|
+
if (fileInput.files.length === 0) {
|
|
433
|
+
await Swal.fire({
|
|
434
|
+
title: "Изменение аватарки",
|
|
435
|
+
text: "Пожалуйста, выберите файл изображения",
|
|
436
|
+
icon: "warning",
|
|
437
|
+
iconColor: "orange",
|
|
438
|
+
confirmButtonColor: "#3085d6",
|
|
439
|
+
confirmButtonText: "Ок",
|
|
440
|
+
}).then((result) => {
|
|
441
|
+
return result;
|
|
442
|
+
});
|
|
443
|
+
return;
|
|
333
444
|
}
|
|
334
|
-
async function uploadAvatar() {
|
|
335
|
-
const fileInput = document.getElementById('avatar-file');
|
|
336
|
-
if (fileInput.files.length === 0) {
|
|
337
|
-
await Swal.fire({title: 'Изменение аватарки', text: 'Пожалуйста, выберите файл изображения', icon: 'warning', iconColor:"orange", confirmButtonColor: '#3085d6', confirmButtonText: 'Ок'}).then((result) => {return result;});
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
445
|
|
|
341
|
-
|
|
342
|
-
|
|
446
|
+
const formData = new FormData();
|
|
447
|
+
formData.append("avatar", fileInput.files[0]);
|
|
343
448
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
449
|
+
try {
|
|
450
|
+
const res = await fetch("/api/bank/users/avatar", {
|
|
451
|
+
method: "POST",
|
|
452
|
+
headers: {
|
|
453
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
454
|
+
},
|
|
455
|
+
body: formData,
|
|
456
|
+
});
|
|
457
|
+
const data = await res.json();
|
|
350
458
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
459
|
+
if (data.status) {
|
|
460
|
+
await Swal.fire({
|
|
461
|
+
title: "Изменение аватарки",
|
|
462
|
+
text: "Аватарка успешно обновлена",
|
|
463
|
+
icon: "sucess",
|
|
464
|
+
confirmButtonColor: "#3085d6",
|
|
465
|
+
confirmButtonText: "Ок",
|
|
466
|
+
});
|
|
467
|
+
Toastify({
|
|
468
|
+
text: `Вы изменили аватарку`,
|
|
469
|
+
duration: 3000,
|
|
470
|
+
style: { background: "rgb(58, 245, 11)" },
|
|
471
|
+
}).showToast();
|
|
472
|
+
document.getElementById("user-avatar").src = data.avatar;
|
|
473
|
+
fileInput.value = "";
|
|
474
|
+
} else {
|
|
475
|
+
await Swal.fire({
|
|
476
|
+
title: "Изменение аватарки",
|
|
477
|
+
text: "Ошибка",
|
|
478
|
+
icon: "warning",
|
|
479
|
+
iconColor: "orange",
|
|
480
|
+
confirmButtonColor: "#3085d6",
|
|
481
|
+
confirmButtonText: "Ок",
|
|
482
|
+
});
|
|
483
|
+
console.log("Ошибка: " + data.message);
|
|
367
484
|
}
|
|
485
|
+
} catch (err) {
|
|
486
|
+
await Swal.fire({
|
|
487
|
+
title: "Изменение аватарки",
|
|
488
|
+
text: "Ошибка. Не удалось загрузить файл на сервер",
|
|
489
|
+
icon: "error",
|
|
490
|
+
confirmButtonColor: "#3085d6",
|
|
491
|
+
confirmButtonText: "Ок",
|
|
492
|
+
});
|
|
493
|
+
console.error(err);
|
|
368
494
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
495
|
+
}
|
|
496
|
+
async function updatePassword() {
|
|
497
|
+
const oldPassword = document.getElementById("p-old").value;
|
|
498
|
+
const newPassword = document.getElementById("p-new").value;
|
|
499
|
+
if (!oldPassword || !newPassword) {
|
|
500
|
+
const t = await Swal.fire({
|
|
501
|
+
title: "Изменение пароля",
|
|
502
|
+
text: "Заполните оба поля паролей",
|
|
503
|
+
icon: "warning",
|
|
504
|
+
iconColor: "orange",
|
|
505
|
+
confirmButtonColor: "#3085d6",
|
|
506
|
+
confirmButtonText: "Ок",
|
|
507
|
+
}).then((result) => {
|
|
508
|
+
return result;
|
|
509
|
+
});
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (user2FaActive) {
|
|
513
|
+
const { value: totpCode } = await Swal.fire({
|
|
514
|
+
title: "Изменение пароля",
|
|
515
|
+
text: "Введите ваш 2FA токен подтверждения для смены пароля",
|
|
516
|
+
icon: "question",
|
|
517
|
+
iconColor: "blue",
|
|
518
|
+
input: "password",
|
|
519
|
+
inputLabel: "Password",
|
|
520
|
+
inputPlaceholder: "Введите ваш 2FA токен",
|
|
521
|
+
inputAttributes: {
|
|
522
|
+
maxlength: "10",
|
|
523
|
+
autocapitalize: "off",
|
|
524
|
+
autocorrect: "off",
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
if (!totpCode) return;
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
const res = await fetch("/api/bank/users/change-password", {
|
|
531
|
+
method: "POST",
|
|
532
|
+
headers: {
|
|
533
|
+
"Content-Type": "application/json",
|
|
534
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
535
|
+
},
|
|
536
|
+
body: JSON.stringify({ oldPassword, newPassword, totpCode }),
|
|
537
|
+
});
|
|
538
|
+
const data = await res.json();
|
|
539
|
+
if (data.status) {
|
|
540
|
+
await Swal.fire({
|
|
541
|
+
title: "Изменение пароля",
|
|
542
|
+
text: "Пароль успешно изменен",
|
|
543
|
+
icon: "sucess",
|
|
544
|
+
confirmButtonColor: "#3085d6",
|
|
545
|
+
confirmButtonText: "Ок",
|
|
546
|
+
});
|
|
547
|
+
document.getElementById("p-old").value = "";
|
|
548
|
+
document.getElementById("p-new").value = "";
|
|
549
|
+
Toastify({
|
|
550
|
+
text: `Вы изменили пароль`,
|
|
551
|
+
duration: 3000,
|
|
552
|
+
style: { background: "rgb(58, 245, 11)" },
|
|
553
|
+
}).showToast();
|
|
554
|
+
} else {
|
|
555
|
+
console.error("Ошибка изменения пароля. Код [" + data.statusCode + "]");
|
|
556
|
+
await Swal.fire({
|
|
557
|
+
title: "Изменение пароля",
|
|
558
|
+
text: "Ошибка изменения пароля",
|
|
559
|
+
icon: "warning",
|
|
560
|
+
iconColor: "orange",
|
|
561
|
+
confirmButtonColor: "#3085d6",
|
|
562
|
+
confirmButtonText: "Ок",
|
|
385
563
|
});
|
|
386
|
-
const data = await res.json();
|
|
387
|
-
if (data.status) {
|
|
388
|
-
await Swal.fire({title:"Изменение пароля", text:"Пароль успешно изменен", icon:"sucess", confirmButtonColor: '#3085d6', confirmButtonText: 'Ок'})
|
|
389
|
-
document.getElementById('p-old').value = "";
|
|
390
|
-
document.getElementById('p-new').value = "";
|
|
391
|
-
Toastify({
|
|
392
|
-
text: `Вы изменили пароль`,
|
|
393
|
-
duration:3000,
|
|
394
|
-
style: { background: "rgb(58, 245, 11)" }
|
|
395
|
-
}).showToast();
|
|
396
|
-
} else {
|
|
397
|
-
console.error("Ошибка изменения пароля. Код [" + data.statusCode + "]");
|
|
398
|
-
await Swal.fire({title:"Изменение пароля", text:"Ошибка изменения пароля", icon:"warning", iconColor:"orange", confirmButtonColor: '#3085d6', confirmButtonText: 'Ок'})
|
|
399
|
-
}
|
|
400
|
-
} catch (err) {
|
|
401
|
-
console.error(err);
|
|
402
564
|
}
|
|
565
|
+
} catch (err) {
|
|
566
|
+
console.error(err);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
async function updateGmail() {
|
|
570
|
+
const newGmail = document.getElementById("profile-new-gmail").value.trim();
|
|
571
|
+
if (!newGmail || !newGmail.includes("@")) {
|
|
572
|
+
await Swal.fire({
|
|
573
|
+
title: "Изменение почты",
|
|
574
|
+
text: "Введите корректный адрес почты!",
|
|
575
|
+
icon: "warning",
|
|
576
|
+
iconColor: "orange",
|
|
577
|
+
confirmButtonColor: "#3085d6",
|
|
578
|
+
confirmButtonText: "Ок",
|
|
579
|
+
}).then((result) => {
|
|
580
|
+
return result;
|
|
581
|
+
});
|
|
582
|
+
return;
|
|
403
583
|
}
|
|
404
|
-
async function updateGmail() {
|
|
405
|
-
const newGmail = document.getElementById('profile-new-gmail').value.trim();
|
|
406
|
-
if (!newGmail || !newGmail.includes('@')) {
|
|
407
|
-
await Swal.fire({title: 'Изменение почты', text: 'Введите корректный адрес почты!', icon: 'warning', iconColor:"orange", confirmButtonColor: '#3085d6', confirmButtonText: 'Ок'}).then((result) => {return result;});
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
584
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
585
|
+
try {
|
|
586
|
+
const res = await fetch("/api/bank/users/change-gmail", {
|
|
587
|
+
method: "POST",
|
|
588
|
+
headers: {
|
|
589
|
+
"Content-Type": "application/json",
|
|
590
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
591
|
+
},
|
|
592
|
+
body: JSON.stringify({ newGmail }),
|
|
593
|
+
});
|
|
594
|
+
const data = await res.json();
|
|
418
595
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
596
|
+
if (data.status) {
|
|
597
|
+
await Swal.fire({
|
|
598
|
+
title: "Изменение почты",
|
|
599
|
+
text: "Адрес Google почты успешно обновлен!",
|
|
600
|
+
icon: "sucess",
|
|
601
|
+
confirmButtonColor: "#3085d6",
|
|
602
|
+
confirmButtonText: "Ок",
|
|
603
|
+
}).then((result) => {
|
|
604
|
+
return result;
|
|
605
|
+
});
|
|
606
|
+
Toastify({
|
|
607
|
+
text: `Вы изменили адрес элекстронной почты`,
|
|
608
|
+
duration: 3000,
|
|
609
|
+
style: { background: "rgb(15, 245, 11)" },
|
|
610
|
+
}).showToast();
|
|
611
|
+
console.log("Адрес Google почты успешно обновлен!");
|
|
612
|
+
document.getElementById("profile-new-gmail").value = "";
|
|
613
|
+
} else {
|
|
614
|
+
await Swal.fire({
|
|
615
|
+
title: "Изменение почты",
|
|
616
|
+
text: "Ошибка смены почты",
|
|
617
|
+
icon: "warning",
|
|
618
|
+
iconColor: "orange",
|
|
619
|
+
confirmButtonColor: "#3085d6",
|
|
620
|
+
confirmButtonText: "Ок",
|
|
621
|
+
}).then((result) => {
|
|
622
|
+
return result;
|
|
623
|
+
});
|
|
624
|
+
console.log("Ошибка смены почты: Код [" + data.statusCode + "]");
|
|
434
625
|
}
|
|
626
|
+
} catch (err) {
|
|
627
|
+
console.error(err);
|
|
435
628
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
syncProfileSectionData();
|
|
629
|
+
}
|
|
630
|
+
async function toggleSystem2FA() {
|
|
631
|
+
const url = user2FaActive
|
|
632
|
+
? "/api/bank/users/disable-2fa"
|
|
633
|
+
: "/api/bank/users/enable-2fa";
|
|
634
|
+
try {
|
|
635
|
+
const res = await fetch(url, {
|
|
636
|
+
method: "POST",
|
|
637
|
+
headers: {
|
|
638
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
639
|
+
},
|
|
640
|
+
});
|
|
641
|
+
const data = await res.json();
|
|
642
|
+
if (data.status) {
|
|
643
|
+
if (!user2FaActive && data.secret2FA) {
|
|
644
|
+
document.getElementById("profile-2fa-setup").style.display = "block";
|
|
645
|
+
document.getElementById("profile-2fa-key").innerText = data.secret2FA;
|
|
646
|
+
Toastify({
|
|
647
|
+
text: `Защита 2FA успешно активирована`,
|
|
648
|
+
duration: 3000,
|
|
649
|
+
style: { background: "rgb(31, 245, 11)" },
|
|
650
|
+
}).showToast();
|
|
459
651
|
} else {
|
|
652
|
+
document.getElementById("profile-2fa-setup").style.display = "none";
|
|
460
653
|
Toastify({
|
|
461
|
-
text:
|
|
462
|
-
duration:3000,
|
|
463
|
-
style: { background: "rgb(245,
|
|
654
|
+
text: `Защита 2FA успешно отключена`,
|
|
655
|
+
duration: 3000,
|
|
656
|
+
style: { background: "rgb(245, 171, 11)" },
|
|
464
657
|
}).showToast();
|
|
465
658
|
}
|
|
466
|
-
|
|
467
|
-
|
|
659
|
+
syncProfileSectionData();
|
|
660
|
+
} else {
|
|
661
|
+
Toastify({
|
|
662
|
+
text: `Ошибка управления 2FA`,
|
|
663
|
+
duration: 3000,
|
|
664
|
+
style: { background: "rgb(245, 11, 11)" },
|
|
665
|
+
}).showToast();
|
|
468
666
|
}
|
|
667
|
+
} catch (err) {
|
|
668
|
+
console.error(err);
|
|
469
669
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
670
|
+
}
|
|
671
|
+
async function closeAccount(accountId) {
|
|
672
|
+
const deleteAccountStatus = await Swal.fire({
|
|
673
|
+
title: "Удаление щёта",
|
|
674
|
+
text: "Вы действительно хотите удалить щёт?",
|
|
675
|
+
icon: "warning",
|
|
676
|
+
iconColor: "orange",
|
|
677
|
+
showCancelButton: true,
|
|
678
|
+
confirmButtonColor: "#d63030",
|
|
679
|
+
cancelButtonColor: "#46d630",
|
|
680
|
+
confirmButtonText: "Да, удалить",
|
|
681
|
+
cancelButtonText: "Отмена",
|
|
682
|
+
}).then((result) => {
|
|
683
|
+
return result;
|
|
684
|
+
});
|
|
685
|
+
if (!deleteAccountStatus.isConfirmed) return;
|
|
686
|
+
try {
|
|
687
|
+
const response = await fetch(`/api/bank/accounts/${accountId}`, {
|
|
688
|
+
method: "DELETE",
|
|
689
|
+
headers: {
|
|
690
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
if (!response.ok) {
|
|
694
|
+
const errorText = await response.text();
|
|
695
|
+
console.error("Критическая ошибка бэкенда:", errorText);
|
|
696
|
+
await Swal.fire({
|
|
697
|
+
title: "Удаление щёта",
|
|
698
|
+
text: "Ошибка при удалении щета. Попробуйте позже...",
|
|
699
|
+
icon: "error",
|
|
700
|
+
iconColor: "orange",
|
|
701
|
+
showCancelButton: true,
|
|
702
|
+
confirmButtonColor: "#307bd6",
|
|
703
|
+
confirmButtonText: "Ок",
|
|
704
|
+
}).then((result) => {
|
|
705
|
+
return result;
|
|
476
706
|
});
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
style: { background: "rgb(46, 245, 11)" }
|
|
489
|
-
}).showToast();
|
|
490
|
-
loadData();
|
|
491
|
-
} else {
|
|
492
|
-
Toastify({
|
|
493
|
-
text: `Ошибка удаления`,
|
|
494
|
-
duration:3000,
|
|
495
|
-
style: { background: "rgb(245, 187, 11)" }
|
|
496
|
-
}).showToast();
|
|
497
|
-
await Swal.fire({title: 'Удаление щёта', text:'Ошибка при удалении щета. Счет должен быть пуст и разблокирован', icon: 'information', iconColor:"orange", showCancelButton: true, confirmButtonColor: '#307bd6', confirmButtonText: 'Ок'}).then((result) => {return result;});
|
|
498
|
-
}
|
|
499
|
-
} catch (err) {
|
|
500
|
-
console.error("Ошибка при закрытии счета:", err);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
const data = await response.json();
|
|
710
|
+
if (data.status) {
|
|
711
|
+
Toastify({
|
|
712
|
+
text: `Щёт успешно удалён`,
|
|
713
|
+
duration: 3000,
|
|
714
|
+
style: { background: "rgb(46, 245, 11)" },
|
|
715
|
+
}).showToast();
|
|
716
|
+
loadData();
|
|
717
|
+
} else {
|
|
501
718
|
Toastify({
|
|
502
719
|
text: `Ошибка удаления`,
|
|
503
|
-
duration:3000,
|
|
504
|
-
style: { background: "rgb(245,
|
|
720
|
+
duration: 3000,
|
|
721
|
+
style: { background: "rgb(245, 187, 11)" },
|
|
505
722
|
}).showToast();
|
|
723
|
+
await Swal.fire({
|
|
724
|
+
title: "Удаление щёта",
|
|
725
|
+
text: "Ошибка при удалении щета. Счет должен быть пуст и разблокирован",
|
|
726
|
+
icon: "information",
|
|
727
|
+
iconColor: "orange",
|
|
728
|
+
showCancelButton: true,
|
|
729
|
+
confirmButtonColor: "#307bd6",
|
|
730
|
+
confirmButtonText: "Ок",
|
|
731
|
+
}).then((result) => {
|
|
732
|
+
return result;
|
|
733
|
+
});
|
|
506
734
|
}
|
|
735
|
+
} catch (err) {
|
|
736
|
+
console.error("Ошибка при закрытии счета:", err);
|
|
737
|
+
Toastify({
|
|
738
|
+
text: `Ошибка удаления`,
|
|
739
|
+
duration: 3000,
|
|
740
|
+
style: { background: "rgb(245, 11, 11)" },
|
|
741
|
+
}).showToast();
|
|
507
742
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
743
|
+
}
|
|
744
|
+
async function loadTransactionLogs() {
|
|
745
|
+
try {
|
|
746
|
+
const response = await fetch("/api/bank/accounts/my-logs", {
|
|
747
|
+
headers: {
|
|
748
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
749
|
+
},
|
|
750
|
+
});
|
|
751
|
+
if (!response.ok) return;
|
|
752
|
+
const result = await response.json();
|
|
753
|
+
const tbody = document.querySelector("#table-logs tbody");
|
|
754
|
+
tbody.innerHTML = "";
|
|
755
|
+
if (result.logs.length === 0) {
|
|
756
|
+
tbody.innerHTML = `<tr><td colspan="5" style="text-align:center; color:#aaa;">У вас пока нет совершенных транзакций</td></tr>`;
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
result.logs.forEach((log) => {
|
|
760
|
+
const tad = new Date(log.createdAt);
|
|
761
|
+
const date = `${tad.getFullYear()}.${tad.getMonth()}.${tad.getDay()}`;
|
|
762
|
+
const time = `${tad.getHours()}:${tad.getMinutes()}:${tad.getSeconds()}`;
|
|
763
|
+
let typeText = "";
|
|
764
|
+
let amountStyle = "";
|
|
765
|
+
let prefix = "";
|
|
766
|
+
let sender = "";
|
|
767
|
+
if (log.type === "DEPOSIT" || log.type === "TRANSFER_IN") {
|
|
768
|
+
typeText = log.type === "DEPOSIT" ? "Пополнение" : "Перевод (Входящий)";
|
|
769
|
+
amountStyle = "color: green; font-weight: bold;";
|
|
770
|
+
prefix = "+";
|
|
771
|
+
} else {
|
|
772
|
+
typeText = log.type === "WITHDRAW" ? "Списание" : "Перевод (Исходящий)";
|
|
773
|
+
amountStyle = "color: red; font-weight: bold;";
|
|
774
|
+
prefix = "-";
|
|
518
775
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
let prefix = "";
|
|
526
|
-
let sender = "";
|
|
527
|
-
if (log.type === 'DEPOSIT' || log.type === 'TRANSFER_IN') {
|
|
528
|
-
typeText = log.type === 'DEPOSIT' ? 'Пополнение' : 'Перевод (Входящий)';
|
|
529
|
-
amountStyle = 'color: green; font-weight: bold;';
|
|
530
|
-
prefix = "+";
|
|
531
|
-
} else {
|
|
532
|
-
typeText = log.type === 'WITHDRAW' ? 'Списание' : 'Перевод (Исходящий)';
|
|
533
|
-
amountStyle = 'color: red; font-weight: bold;';
|
|
534
|
-
prefix = "-";
|
|
535
|
-
}
|
|
536
|
-
if(log.sender_or_receiver == "Администрация (ROOT)"){
|
|
537
|
-
sender = `<span style='color:blue' id='root001'><strong><i>Администрация (ROOT)</i></strong></span>`
|
|
538
|
-
} else {
|
|
539
|
-
sender = log.sender_or_receiver;
|
|
540
|
-
}
|
|
541
|
-
tbody.innerHTML += `
|
|
776
|
+
if (log.sender_or_receiver == "Администрация (ROOT)") {
|
|
777
|
+
sender = `<span style='color:blue' id='root001'><strong><i>Администрация (ROOT)</i></strong></span>`;
|
|
778
|
+
} else {
|
|
779
|
+
sender = log.sender_or_receiver;
|
|
780
|
+
}
|
|
781
|
+
tbody.innerHTML += `
|
|
542
782
|
<tr>
|
|
543
783
|
<td>${date}</td>
|
|
544
784
|
<td>${time}</td>
|
|
545
785
|
<td><b>#${log.account_id}</b></td>
|
|
546
786
|
<td>${typeText}</td>
|
|
547
|
-
<td>${sender ||
|
|
787
|
+
<td>${sender || "—"}</td>
|
|
548
788
|
<td style="${amountStyle}">${prefix} ${parseFloat(log.amount).toFixed(2)} руб.</td>
|
|
549
789
|
</tr>
|
|
550
790
|
`;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
}
|
|
791
|
+
});
|
|
792
|
+
} catch (err) {
|
|
793
|
+
console.error("Ошибка загрузки истории операций:", err);
|
|
555
794
|
}
|
|
556
|
-
|
|
795
|
+
}
|
|
796
|
+
async function executeCreateCredit() {
|
|
557
797
|
try {
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
const
|
|
562
|
-
const
|
|
563
|
-
const termMonthsField = document.getElementById('credit-termMonths');
|
|
564
|
-
|
|
565
|
-
// Проверка на случай, если элементы не найдены в DOM (чтобы скрипт не падал)
|
|
798
|
+
const accountIdField = document.getElementById("credit-accountId");
|
|
799
|
+
const amountField = document.getElementById("credit-amount");
|
|
800
|
+
const autoRepayField = document.getElementById("credit-autoRepay");
|
|
801
|
+
const rateField = document.getElementById("credit-rate");
|
|
802
|
+
const termMonthsField = document.getElementById("credit-termMonths");
|
|
566
803
|
if (!accountIdField || !amountField) {
|
|
567
|
-
console.error(
|
|
804
|
+
console.error(
|
|
805
|
+
"Критические элементы формы кредита не найдены в HTML дерева.",
|
|
806
|
+
);
|
|
568
807
|
return;
|
|
569
808
|
}
|
|
570
|
-
|
|
571
|
-
// 2. Формируем чистые переменные
|
|
572
809
|
const accountId = accountIdField.value;
|
|
573
810
|
const amount = parseFloat(amountField.value);
|
|
574
|
-
const autoRepay = autoRepayField ? autoRepayField.checked : false;
|
|
811
|
+
const autoRepay = autoRepayField ? autoRepayField.checked : false;
|
|
575
812
|
const rate = rateField ? parseFloat(rateField.value) : 0.1;
|
|
576
|
-
const termMonths = termMonthsField
|
|
577
|
-
|
|
578
|
-
|
|
813
|
+
const termMonths = termMonthsField
|
|
814
|
+
? parseInt(termMonthsField.value, 10)
|
|
815
|
+
: 12;
|
|
579
816
|
if (!accountId || isNaN(amount) || amount <= 0) {
|
|
580
|
-
Swal.fire(
|
|
817
|
+
Swal.fire(
|
|
818
|
+
"Внимание",
|
|
819
|
+
"Пожалуйста, заполните номер счета и корректную сумму",
|
|
820
|
+
"warning",
|
|
821
|
+
);
|
|
581
822
|
return;
|
|
582
823
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
'Content-Type': 'application/json'
|
|
589
|
-
// Если вы используете JWT или сессии, сюда может потребоваться передать токен авторизации
|
|
824
|
+
const response = await fetch("/api/bank/accounts/credit", {
|
|
825
|
+
method: "POST",
|
|
826
|
+
headers: {
|
|
827
|
+
"Content-Type": "application/json",
|
|
828
|
+
"Bypass-Tunnel-Reminder": "true",
|
|
590
829
|
},
|
|
591
830
|
body: JSON.stringify({
|
|
592
831
|
accountId,
|
|
593
832
|
amount,
|
|
594
833
|
autoRepay,
|
|
595
834
|
rate,
|
|
596
|
-
termMonths
|
|
597
|
-
})
|
|
835
|
+
termMonths,
|
|
836
|
+
}),
|
|
598
837
|
});
|
|
599
|
-
|
|
600
838
|
if (!response.ok) {
|
|
601
839
|
throw new Error(`Ошибка сервера: ${response.status}`);
|
|
602
840
|
}
|
|
603
|
-
|
|
604
841
|
const result = await response.json();
|
|
605
|
-
|
|
606
|
-
// 4. Обработка ответа от бэкенда
|
|
607
842
|
if (result.success) {
|
|
608
|
-
Swal.fire(
|
|
609
|
-
|
|
610
|
-
|
|
843
|
+
Swal.fire(
|
|
844
|
+
"Успех!",
|
|
845
|
+
result.message || "Кредит успешно оформлен",
|
|
846
|
+
"success",
|
|
847
|
+
).then(() => {
|
|
848
|
+
location.href = "/bank/accounts.html";
|
|
611
849
|
});
|
|
612
850
|
} else {
|
|
613
|
-
Swal.fire(
|
|
851
|
+
Swal.fire(
|
|
852
|
+
"Ошибка оформления",
|
|
853
|
+
result.error || "Не удалось выдать кредит",
|
|
854
|
+
"error",
|
|
855
|
+
);
|
|
614
856
|
}
|
|
615
|
-
|
|
616
857
|
} catch (error) {
|
|
617
|
-
console.error(
|
|
618
|
-
Swal.fire(
|
|
858
|
+
console.error("Ошибка выполнения executeCreateCredit:", error);
|
|
859
|
+
Swal.fire(
|
|
860
|
+
"Критическая ошибка",
|
|
861
|
+
"Не удалось связаться с сервером банка",
|
|
862
|
+
"error",
|
|
863
|
+
);
|
|
619
864
|
}
|
|
620
865
|
}
|