artew-exam-template-2006 1.0.0

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/index.js ADDED
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const projectName = process.argv[2];
7
+
8
+ if (!projectName) {
9
+ console.log("Ошибка: укажи название проекта");
10
+ console.log("Пример: npx exam-template my-app");
11
+ process.exit(1);
12
+ }
13
+
14
+ const targetPath = path.join(process.cwd(), projectName);
15
+ const templatePath = path.join(__dirname, "template");
16
+
17
+ if (fs.existsSync(targetPath)) {
18
+ console.log("Ошибка: папка уже существует:", projectName);
19
+ process.exit(1);
20
+ }
21
+
22
+ function copyFolder(from, to) {
23
+ fs.mkdirSync(to, { recursive: true });
24
+
25
+ const files = fs.readdirSync(from);
26
+
27
+ for (const file of files) {
28
+ const fromPath = path.join(from, file);
29
+ const toPath = path.join(to, file);
30
+
31
+ if (fs.statSync(fromPath).isDirectory()) {
32
+ copyFolder(fromPath, toPath);
33
+ } else {
34
+ fs.copyFileSync(fromPath, toPath);
35
+ }
36
+ }
37
+ }
38
+
39
+ copyFolder(templatePath, targetPath);
40
+
41
+ console.log("Проект создан:", projectName);
42
+ console.log("");
43
+ console.log("Дальше пиши:");
44
+ console.log(`cd ${projectName}`);
45
+ console.log("npm install");
46
+ console.log("npm run dev");
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "artew-exam-template-2006",
3
+ "version": "1.0.0",
4
+ "description": "Exam project generator",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "artew-exam": "./index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "template"
12
+ ],
13
+ "keywords": [
14
+ "exam",
15
+ "template",
16
+ "generator"
17
+ ],
18
+ "author": "artew",
19
+ "license": "ISC"
20
+ }
@@ -0,0 +1,36 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Админ-панель | Exam App</title>
7
+ <link rel="stylesheet" href="css/style.css" />
8
+ </head>
9
+ <body>
10
+ <header class="header">
11
+ <div class="logo">Админ-панель</div>
12
+ <nav>
13
+ <a href="index.html">Главная</a>
14
+ <a href="profile.html">Мои заявки</a>
15
+ <a href="#" onclick="logout()">Выйти</a>
16
+ </nav>
17
+ </header>
18
+
19
+ <main class="page">
20
+ <h1>Все заявки пользователей</h1>
21
+
22
+ <div class="toolbar">
23
+ <button class="btn secondary" onclick="createDemoData()">Создать демо-данные</button>
24
+ <button class="btn secondary" onclick="resetData()">Очистить данные</button>
25
+ <input id="search" type="text" placeholder="Поиск по курсу или пользователю" />
26
+ <select id="statusFilter"></select>
27
+ </div>
28
+
29
+ <div class="grid" id="adminRequests"></div>
30
+ </main>
31
+
32
+ <script src="js/config.js"></script>
33
+ <script src="js/main.js"></script>
34
+ <script src="js/app.js"></script>
35
+ </body>
36
+ </html>
@@ -0,0 +1,324 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ font-family: Inter, Arial, sans-serif;
6
+ }
7
+
8
+ body {
9
+ min-height: 100vh;
10
+ background:
11
+ radial-gradient(circle at top left, rgba(59, 130, 246, 0.22), transparent 35%),
12
+ linear-gradient(135deg, #0f172a, #111827);
13
+ color: #f8fafc;
14
+ }
15
+
16
+ a {
17
+ color: inherit;
18
+ text-decoration: none;
19
+ }
20
+
21
+ .header {
22
+ width: calc(100% - 48px);
23
+ margin: 24px auto 0;
24
+ padding: 18px 24px;
25
+ border: 1px solid rgba(255,255,255,0.08);
26
+ border-radius: 22px;
27
+ background: rgba(15, 23, 42, 0.78);
28
+ backdrop-filter: blur(16px);
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: space-between;
32
+ }
33
+
34
+ .logo {
35
+ font-size: 22px;
36
+ font-weight: 800;
37
+ letter-spacing: -0.5px;
38
+ }
39
+
40
+ nav {
41
+ display: flex;
42
+ gap: 10px;
43
+ flex-wrap: wrap;
44
+ }
45
+
46
+ nav a {
47
+ padding: 10px 14px;
48
+ border-radius: 12px;
49
+ color: #cbd5e1;
50
+ font-size: 14px;
51
+ transition: 0.2s;
52
+ }
53
+
54
+ nav a:hover {
55
+ background: rgba(59,130,246,0.18);
56
+ color: white;
57
+ }
58
+
59
+ .hero {
60
+ min-height: calc(100vh - 120px);
61
+ padding: 70px 7vw;
62
+ display: grid;
63
+ grid-template-columns: 1.3fr 0.7fr;
64
+ gap: 36px;
65
+ align-items: center;
66
+ }
67
+
68
+ .hero-content h1 {
69
+ max-width: 850px;
70
+ font-size: clamp(38px, 5vw, 72px);
71
+ line-height: 1;
72
+ letter-spacing: -2px;
73
+ margin: 18px 0;
74
+ }
75
+
76
+ .hero-content p {
77
+ max-width: 680px;
78
+ color: #cbd5e1;
79
+ font-size: 18px;
80
+ line-height: 1.7;
81
+ }
82
+
83
+ .badge {
84
+ display: inline-flex;
85
+ padding: 9px 14px;
86
+ border-radius: 999px;
87
+ background: rgba(59,130,246,0.16);
88
+ border: 1px solid rgba(96,165,250,0.28);
89
+ color: #93c5fd;
90
+ font-weight: 700;
91
+ }
92
+
93
+ .actions {
94
+ margin-top: 30px;
95
+ display: flex;
96
+ gap: 14px;
97
+ flex-wrap: wrap;
98
+ }
99
+
100
+ .btn {
101
+ display: inline-flex;
102
+ align-items: center;
103
+ justify-content: center;
104
+ min-height: 46px;
105
+ padding: 13px 20px;
106
+ border: 0;
107
+ border-radius: 14px;
108
+ background: linear-gradient(135deg, #2563eb, #7c3aed);
109
+ color: white;
110
+ font-weight: 800;
111
+ cursor: pointer;
112
+ box-shadow: 0 18px 40px rgba(37, 99, 235, 0.28);
113
+ transition: 0.2s;
114
+ }
115
+
116
+ .btn:hover {
117
+ transform: translateY(-2px);
118
+ }
119
+
120
+ .btn.secondary {
121
+ background: rgba(255,255,255,0.08);
122
+ box-shadow: none;
123
+ border: 1px solid rgba(255,255,255,0.1);
124
+ }
125
+
126
+ .hero-card,
127
+ .form-card,
128
+ .request-card {
129
+ background: rgba(30, 41, 59, 0.72);
130
+ border: 1px solid rgba(255,255,255,0.08);
131
+ border-radius: 26px;
132
+ box-shadow: 0 24px 70px rgba(0,0,0,0.25);
133
+ }
134
+
135
+ .hero-card {
136
+ padding: 32px;
137
+ }
138
+
139
+ .hero-card h2 {
140
+ margin-bottom: 18px;
141
+ font-size: 28px;
142
+ }
143
+
144
+ .hero-card li {
145
+ list-style: none;
146
+ margin-bottom: 14px;
147
+ color: #dbeafe;
148
+ padding: 14px;
149
+ border-radius: 14px;
150
+ background: rgba(255,255,255,0.055);
151
+ }
152
+
153
+ .footer {
154
+ padding: 28px;
155
+ text-align: center;
156
+ color: #94a3b8;
157
+ }
158
+
159
+ .auth-page,
160
+ .page {
161
+ padding: 50px 7vw;
162
+ }
163
+
164
+ .auth-page {
165
+ min-height: 100vh;
166
+ display: grid;
167
+ place-items: center;
168
+ }
169
+
170
+ .form-card {
171
+ width: 100%;
172
+ max-width: 480px;
173
+ padding: 34px;
174
+ }
175
+
176
+ .form-card.wide {
177
+ max-width: 720px;
178
+ }
179
+
180
+ .form-card h1,
181
+ .page h1 {
182
+ font-size: 34px;
183
+ margin-bottom: 10px;
184
+ }
185
+
186
+ .form-card p {
187
+ color: #94a3b8;
188
+ margin-bottom: 24px;
189
+ }
190
+
191
+ label {
192
+ display: block;
193
+ margin: 16px 0 8px;
194
+ color: #cbd5e1;
195
+ font-weight: 700;
196
+ }
197
+
198
+ input,
199
+ select {
200
+ width: 100%;
201
+ min-height: 46px;
202
+ padding: 12px 14px;
203
+ border: 1px solid rgba(255,255,255,0.1);
204
+ border-radius: 14px;
205
+ background: rgba(15, 23, 42, 0.8);
206
+ color: white;
207
+ outline: none;
208
+ }
209
+
210
+ input:focus,
211
+ select:focus {
212
+ border-color: #60a5fa;
213
+ }
214
+
215
+ .error {
216
+ min-height: 22px;
217
+ margin: 14px 0;
218
+ color: #fca5a5;
219
+ font-weight: 700;
220
+ }
221
+
222
+ .link {
223
+ display: block;
224
+ margin-top: 18px;
225
+ color: #93c5fd;
226
+ text-align: center;
227
+ }
228
+
229
+ .grid {
230
+ margin-top: 28px;
231
+ display: grid;
232
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
233
+ gap: 22px;
234
+ }
235
+
236
+ .request-card {
237
+ padding: 24px;
238
+ }
239
+
240
+ .request-card h3 {
241
+ font-size: 21px;
242
+ margin-bottom: 14px;
243
+ }
244
+
245
+ .request-card p {
246
+ color: #cbd5e1;
247
+ margin-bottom: 8px;
248
+ }
249
+
250
+ .status {
251
+ display: inline-flex;
252
+ margin-top: 12px;
253
+ padding: 8px 12px;
254
+ border-radius: 999px;
255
+ background: rgba(34,197,94,0.16);
256
+ color: #86efac;
257
+ font-weight: 800;
258
+ }
259
+
260
+ .toolbar {
261
+ margin-top: 24px;
262
+ display: grid;
263
+ grid-template-columns: 1fr 260px;
264
+ gap: 16px;
265
+ }
266
+
267
+ @media (max-width: 800px) {
268
+ .header {
269
+ align-items: flex-start;
270
+ flex-direction: column;
271
+ gap: 16px;
272
+ }
273
+
274
+ .hero {
275
+ grid-template-columns: 1fr;
276
+ padding-top: 40px;
277
+ }
278
+
279
+ .toolbar {
280
+ grid-template-columns: 1fr;
281
+ }
282
+ }
283
+ textarea {
284
+ width: 100%;
285
+ min-height: 110px;
286
+ margin-top: 8px;
287
+ padding: 12px 14px;
288
+ resize: vertical;
289
+ border: 1px solid rgba(255,255,255,0.1);
290
+ border-radius: 14px;
291
+ background: rgba(15, 23, 42, 0.8);
292
+ color: white;
293
+ outline: none;
294
+ }
295
+
296
+ textarea:focus {
297
+ border-color: #60a5fa;
298
+ }
299
+
300
+ .card-top {
301
+ display: flex;
302
+ align-items: flex-start;
303
+ justify-content: space-between;
304
+ gap: 16px;
305
+ margin-bottom: 12px;
306
+ }
307
+
308
+ .notice {
309
+ margin-top: 16px;
310
+ padding: 12px 14px;
311
+ border-radius: 14px;
312
+ background: rgba(59,130,246,0.12);
313
+ color: #bfdbfe;
314
+ }
315
+
316
+ .empty-card {
317
+ max-width: 520px;
318
+ }
319
+
320
+ .btn.small {
321
+ margin-top: 12px;
322
+ min-height: 40px;
323
+ padding: 10px 16px;
324
+ }
@@ -0,0 +1,75 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Exam Project</title>
7
+ <link rel="stylesheet" href="css/style.css"/>
8
+ </head>
9
+ <body>
10
+
11
+ <header class="header">
12
+ <div class="logo" data-config="appName">Корочки.есть</div>
13
+
14
+ <nav>
15
+ <a href="index.html">Главная</a>
16
+ <a href="request.html">Заявка</a>
17
+ <a href="profile.html">Мои заявки</a>
18
+ <a href="admin.html">Админ</a>
19
+ <a href="login.html">Вход</a>
20
+ </nav>
21
+ </header>
22
+
23
+ <main class="hero">
24
+
25
+ <section class="hero-content">
26
+
27
+ <span class="badge" data-config="heroBadge">
28
+ Онлайн-обучение
29
+ </span>
30
+
31
+ <h1 data-config="heroTitle">
32
+ Портал записи на курсы дополнительного образования
33
+ </h1>
34
+
35
+ <p data-config="heroText">
36
+ Удобная система для регистрации пользователей, подачи заявок,
37
+ просмотра статусов обучения и управления заявками администратором.
38
+ </p>
39
+
40
+ <div class="actions">
41
+ <a class="btn" href="register.html">
42
+ Зарегистрироваться
43
+ </a>
44
+
45
+ <a class="btn secondary" href="login.html">
46
+ Войти
47
+ </a>
48
+ </div>
49
+
50
+ </section>
51
+
52
+ <section class="hero-card">
53
+
54
+ <h2>Возможности</h2>
55
+
56
+ <ul>
57
+ <li>Регистрация и авторизация</li>
58
+ <li>Формирование заявки</li>
59
+ <li>Просмотр собственных заявок</li>
60
+ <li>Панель администратора</li>
61
+ </ul>
62
+
63
+ </section>
64
+
65
+ </main>
66
+
67
+ <footer class="footer">
68
+ © 2025 Все права защищены
69
+ </footer>
70
+
71
+ <script src="js/config.js"></script>
72
+ <script src="js/main.js"></script>
73
+
74
+ </body>
75
+ </html>
@@ -0,0 +1,261 @@
1
+ const currentUser = localStorage.getItem("currentUser");
2
+
3
+ function getRequests() {
4
+ return JSON.parse(localStorage.getItem("requests")) || [];
5
+ }
6
+
7
+ function saveRequests(requests) {
8
+ localStorage.setItem("requests", JSON.stringify(requests));
9
+ }
10
+
11
+ function showEmpty(container, text) {
12
+ container.innerHTML = `
13
+ <div class="request-card empty-card">
14
+ <h3>Пока пусто</h3>
15
+ <p>${text}</p>
16
+ </div>
17
+ `;
18
+ }
19
+
20
+ function logout() {
21
+ localStorage.removeItem("currentUser");
22
+ window.location.href = "login.html";
23
+ }
24
+
25
+ const requestForm = document.getElementById("requestForm");
26
+
27
+ if (requestForm) {
28
+ if (!currentUser || currentUser === "admin") {
29
+ alert("Сначала войдите как пользователь");
30
+ window.location.href = "login.html";
31
+ }
32
+
33
+ requestForm.addEventListener("submit", (e) => {
34
+ e.preventDefault();
35
+
36
+ const course = document.getElementById("course").value;
37
+ const date = document.getElementById("date").value.trim();
38
+ const payment = document.getElementById("payment").value;
39
+ const error = document.getElementById("error");
40
+
41
+ error.textContent = "";
42
+
43
+ if (!course || !date || !payment) {
44
+ error.textContent = "Заполните все поля";
45
+ return;
46
+ }
47
+
48
+ const datePattern = /^\d{2}\.\d{2}\.\d{4}$/;
49
+
50
+ if (!datePattern.test(date)) {
51
+ error.textContent = "Введите дату в формате ДД.ММ.ГГГГ";
52
+ return;
53
+ }
54
+
55
+ const requests = getRequests();
56
+
57
+ requests.push({
58
+ id: Date.now(),
59
+ user: currentUser,
60
+ course,
61
+ date,
62
+ payment,
63
+ status: "Новая",
64
+ review: "",
65
+ createdAt: new Date().toLocaleString("ru-RU"),
66
+ });
67
+
68
+ saveRequests(requests);
69
+
70
+ alert("Заявка успешно отправлена");
71
+ window.location.href = "profile.html";
72
+ });
73
+ }
74
+
75
+ const myRequests = document.getElementById("myRequests");
76
+
77
+ if (myRequests) {
78
+ if (!currentUser || currentUser === "admin") {
79
+ alert("Сначала войдите как пользователь");
80
+ window.location.href = "login.html";
81
+ }
82
+
83
+ const requests = getRequests().filter((r) => r.user === currentUser);
84
+
85
+ if (requests.length === 0) {
86
+ showEmpty(myRequests, "Вы еще не отправляли заявки на обучение.");
87
+ } else {
88
+ myRequests.innerHTML = requests
89
+ .map((r) => {
90
+ const canReview = r.status === "Обучение завершено";
91
+
92
+ return `
93
+ <div class="request-card">
94
+ <div class="card-top">
95
+ <h3>${r.course}</h3>
96
+ <span class="status">${r.status}</span>
97
+ </div>
98
+
99
+ <p><b>Дата начала:</b> ${r.date}</p>
100
+ <p><b>Оплата:</b> ${r.payment}</p>
101
+ <p><b>Создано:</b> ${r.createdAt || "—"}</p>
102
+
103
+ ${
104
+ canReview
105
+ ? `
106
+ <label>Отзыв о курсе</label>
107
+ <textarea id="review-${r.id}" placeholder="Напишите отзыв">${r.review || ""}</textarea>
108
+ <button class="btn small" onclick="saveReview(${r.id})">Сохранить отзыв</button>
109
+ `
110
+ : `
111
+ <div class="notice">
112
+ Отзыв будет доступен после завершения обучения
113
+ </div>
114
+ `
115
+ }
116
+ </div>
117
+ `;
118
+ })
119
+ .join("");
120
+ }
121
+
122
+ window.saveReview = function (id) {
123
+ const requests = getRequests();
124
+ const request = requests.find((r) => r.id === id);
125
+ const textarea = document.getElementById(`review-${id}`);
126
+
127
+ request.review = textarea.value.trim();
128
+
129
+ saveRequests(requests);
130
+
131
+ alert("Отзыв сохранен");
132
+ };
133
+ }
134
+
135
+ const adminRequests = document.getElementById("adminRequests");
136
+
137
+ if (adminRequests) {
138
+ if (currentUser !== "admin") {
139
+ alert("Доступ только для администратора");
140
+ window.location.href = "login.html";
141
+ }
142
+
143
+ let requests = getRequests();
144
+
145
+ function renderRequests(data) {
146
+ if (data.length === 0) {
147
+ showEmpty(adminRequests, "Заявки не найдены.");
148
+ return;
149
+ }
150
+
151
+ adminRequests.innerHTML = data
152
+ .map((r) => {
153
+ return `
154
+ <div class="request-card">
155
+ <div class="card-top">
156
+ <h3>${r.course}</h3>
157
+ <span class="status">${r.status}</span>
158
+ </div>
159
+
160
+ <p><b>Пользователь:</b> ${r.user}</p>
161
+ <p><b>Дата начала:</b> ${r.date}</p>
162
+ <p><b>Оплата:</b> ${r.payment}</p>
163
+ <p><b>Создано:</b> ${r.createdAt || "—"}</p>
164
+
165
+ <label>Статус заявки</label>
166
+ <select onchange="changeStatus(${r.id}, this.value)">
167
+ ${APP_CONFIG.statuses
168
+ .map(
169
+ (status) => `
170
+ <option ${r.status === status ? "selected" : ""}>
171
+ ${status}
172
+ </option>
173
+ `
174
+ )
175
+ .join("")}
176
+ </select>
177
+
178
+ ${
179
+ r.review
180
+ ? `<div class="notice"><b>Отзыв:</b> ${r.review}</div>`
181
+ : `<div class="notice">Отзыва пока нет</div>`
182
+ }
183
+ </div>
184
+ `;
185
+ })
186
+ .join("");
187
+ }
188
+
189
+ renderRequests(requests);
190
+
191
+ window.changeStatus = function (id, value) {
192
+ const requests = getRequests();
193
+ const request = requests.find((r) => r.id === id);
194
+
195
+ request.status = value;
196
+
197
+ saveRequests(requests);
198
+
199
+ alert("Статус обновлен");
200
+
201
+ location.reload();
202
+ };
203
+
204
+ const search = document.getElementById("search");
205
+ const statusFilter = document.getElementById("statusFilter");
206
+
207
+ function filterData() {
208
+ const searchValue = search.value.toLowerCase();
209
+ const statusValue = statusFilter.value;
210
+
211
+ const filtered = getRequests().filter((r) => {
212
+ const matchesSearch =
213
+ r.course.toLowerCase().includes(searchValue) ||
214
+ r.user.toLowerCase().includes(searchValue);
215
+
216
+ const matchesStatus = !statusValue || r.status === statusValue;
217
+
218
+ return matchesSearch && matchesStatus;
219
+ });
220
+
221
+ renderRequests(filtered);
222
+ }
223
+
224
+ search.addEventListener("input", filterData);
225
+ statusFilter.addEventListener("change", filterData);
226
+ }
227
+ function createDemoData() {
228
+ const demoRequests = [
229
+ {
230
+ id: Date.now() + 1,
231
+ user: "demoUser",
232
+ course: APP_CONFIG.courses[0],
233
+ date: "01.06.2026",
234
+ payment: APP_CONFIG.paymentMethods[0],
235
+ status: APP_CONFIG.statuses[0],
236
+ review: "",
237
+ createdAt: new Date().toLocaleString("ru-RU"),
238
+ },
239
+ {
240
+ id: Date.now() + 2,
241
+ user: "demoUser",
242
+ course: APP_CONFIG.courses[1],
243
+ date: "05.06.2026",
244
+ payment: APP_CONFIG.paymentMethods[1],
245
+ status: APP_CONFIG.statuses[1],
246
+ review: "",
247
+ createdAt: new Date().toLocaleString("ru-RU"),
248
+ },
249
+ ];
250
+
251
+ localStorage.setItem("requests", JSON.stringify(demoRequests));
252
+ alert("Демо-данные созданы");
253
+ location.reload();
254
+ }
255
+ function resetData() {
256
+ localStorage.clear();
257
+
258
+ alert("Все данные очищены");
259
+
260
+ window.location.href = "index.html";
261
+ }
@@ -0,0 +1,79 @@
1
+ const registerForm = document.getElementById("registerForm");
2
+ const loginForm = document.getElementById("loginForm");
3
+
4
+ if (registerForm) {
5
+ registerForm.addEventListener("submit", (e) => {
6
+ e.preventDefault();
7
+
8
+ const login = document.getElementById("login").value.trim();
9
+ const password = document.getElementById("password").value.trim();
10
+ const fullName = document.getElementById("fullName").value.trim();
11
+ const phone = document.getElementById("phone").value.trim();
12
+ const email = document.getElementById("email").value.trim();
13
+ const error = document.getElementById("error");
14
+
15
+ if (!login || !password || !fullName || !phone || !email) {
16
+ error.textContent = "Заполните все поля";
17
+ return;
18
+ }
19
+
20
+ if (login.length < 6) {
21
+ error.textContent = "Логин минимум 6 символов";
22
+ return;
23
+ }
24
+
25
+ if (password.length < 8) {
26
+ error.textContent = "Пароль минимум 8 символов";
27
+ return;
28
+ }
29
+
30
+ const users = JSON.parse(localStorage.getItem("users")) || [];
31
+ const exists = users.find((u) => u.login === login);
32
+
33
+ if (exists) {
34
+ error.textContent = "Пользователь уже существует";
35
+ return;
36
+ }
37
+
38
+ users.push({
39
+ login,
40
+ password,
41
+ fullName,
42
+ phone,
43
+ email,
44
+ });
45
+
46
+ localStorage.setItem("users", JSON.stringify(users));
47
+ localStorage.setItem("currentUser", login);
48
+
49
+ alert("Регистрация успешна");
50
+ window.location.href = "profile.html";
51
+ });
52
+ }
53
+
54
+ if (loginForm) {
55
+ loginForm.addEventListener("submit", (e) => {
56
+ e.preventDefault();
57
+
58
+ const login = document.getElementById("login").value.trim();
59
+ const password = document.getElementById("password").value.trim();
60
+ const error = document.getElementById("error");
61
+
62
+ if (login === APP_CONFIG.adminLogin && password === APP_CONFIG.adminPassword) {
63
+ localStorage.setItem("currentUser", "admin");
64
+ window.location.href = "admin.html";
65
+ return;
66
+ }
67
+
68
+ const users = JSON.parse(localStorage.getItem("users")) || [];
69
+ const user = users.find((u) => u.login === login && u.password === password);
70
+
71
+ if (!user) {
72
+ error.textContent = "Неверный логин или пароль";
73
+ return;
74
+ }
75
+
76
+ localStorage.setItem("currentUser", login);
77
+ window.location.href = "profile.html";
78
+ });
79
+ }
@@ -0,0 +1,30 @@
1
+ const APP_CONFIG = {
2
+ appName: "Корочки.есть",
3
+ heroBadge: "Онлайн-обучение",
4
+ heroTitle: "Портал записи на курсы дополнительного образования",
5
+ heroText:
6
+ "Удобная система для регистрации пользователей, подачи заявок, просмотра статусов обучения и управления заявками администратором.",
7
+
8
+ entityName: "заявка",
9
+ entityNamePlural: "заявки",
10
+
11
+ courses: [
12
+ "Основы алгоритмизации и программирования",
13
+ "Основы веб-дизайна",
14
+ "Основы проектирования баз данных",
15
+ ],
16
+
17
+ paymentMethods: [
18
+ "Наличными",
19
+ "Переводом по номеру телефона",
20
+ ],
21
+
22
+ statuses: [
23
+ "Новая",
24
+ "Идет обучение",
25
+ "Обучение завершено",
26
+ ],
27
+
28
+ adminLogin: "Admin",
29
+ adminPassword: "KorokNET",
30
+ };
@@ -0,0 +1,22 @@
1
+ document.querySelectorAll("[data-config]").forEach((el) => {
2
+ const key = el.dataset.config;
3
+
4
+ if (APP_CONFIG[key]) {
5
+ el.textContent = APP_CONFIG[key];
6
+ }
7
+ });
8
+
9
+ function fillSelect(selectId, items, placeholder) {
10
+ const select = document.getElementById(selectId);
11
+
12
+ if (!select) return;
13
+
14
+ select.innerHTML = `
15
+ <option value="">${placeholder}</option>
16
+ ${items.map((item) => `<option>${item}</option>`).join("")}
17
+ `;
18
+ }
19
+
20
+ fillSelect("course", APP_CONFIG.courses, "Выберите курс");
21
+ fillSelect("payment", APP_CONFIG.paymentMethods, "Выберите способ оплаты");
22
+ fillSelect("statusFilter", APP_CONFIG.statuses, "Все статусы");
@@ -0,0 +1,32 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Вход | Exam App</title>
7
+ <link rel="stylesheet" href="css/style.css" />
8
+ </head>
9
+ <body>
10
+ <main class="auth-page">
11
+ <form class="form-card" id="loginForm">
12
+ <h1>Авторизация</h1>
13
+ <p>Войдите в систему</p>
14
+
15
+ <label>Логин</label>
16
+ <input id="login" type="text" placeholder="Введите логин" />
17
+
18
+ <label>Пароль</label>
19
+ <input id="password" type="password" placeholder="Введите пароль" />
20
+
21
+ <div class="error" id="error"></div>
22
+
23
+ <button class="btn" type="submit">Войти</button>
24
+
25
+ <a class="link" href="register.html">Еще не зарегистрированы? Регистрация</a>
26
+ </form>
27
+ </main>
28
+
29
+ <script src="js/config.js"></script>
30
+ <script src="js/auth.js"></script>
31
+ </body>
32
+ </html>
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "exam-project",
3
+ "version": "1.0.0",
4
+ "scripts": {
5
+ "dev": "node server.js"
6
+ },
7
+ "dependencies": {
8
+ "express": "^4.19.2"
9
+ }
10
+ }
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Мои заявки | Exam App</title>
7
+ <link rel="stylesheet" href="css/style.css" />
8
+ </head>
9
+ <body>
10
+ <header class="header">
11
+ <div class="logo">Корочки.есть</div>
12
+ <nav>
13
+ <a href="index.html">Главная</a>
14
+ <a href="request.html">Заявка</a>
15
+ <a href="profile.html">Мои заявки</a>
16
+ <a href="admin.html">Админ</a>
17
+ </nav>
18
+ </header>
19
+
20
+ <main class="page">
21
+ <h1>Мои заявки</h1>
22
+ <div class="grid" id="myRequests"></div>
23
+ </main>
24
+
25
+ <script src="js/config.js"></script>
26
+ <script src="js/app.js"></script>
27
+ </body>
28
+ </html>
@@ -0,0 +1,41 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Регистрация | Exam App</title>
7
+ <link rel="stylesheet" href="css/style.css" />
8
+ </head>
9
+ <body>
10
+ <main class="auth-page">
11
+ <form class="form-card" id="registerForm">
12
+ <h1>Регистрация</h1>
13
+ <p>Создайте учетную запись пользователя</p>
14
+
15
+ <label>Логин</label>
16
+ <input id="login" type="text" placeholder="Минимум 6 символов" />
17
+
18
+ <label>Пароль</label>
19
+ <input id="password" type="password" placeholder="Минимум 8 символов" />
20
+
21
+ <label>ФИО</label>
22
+ <input id="fullName" type="text" placeholder="Иванов Иван Иванович" />
23
+
24
+ <label>Телефон</label>
25
+ <input id="phone" type="text" placeholder="8(999)999-99-99" />
26
+
27
+ <label>Email</label>
28
+ <input id="email" type="email" placeholder="example@mail.ru" />
29
+
30
+ <div class="error" id="error"></div>
31
+
32
+ <button class="btn" type="submit">Зарегистрироваться</button>
33
+
34
+ <a class="link" href="login.html">Уже есть аккаунт? Войти</a>
35
+ </form>
36
+ </main>
37
+
38
+ <script src="js/config.js"></script>
39
+ <script src="js/auth.js"></script>
40
+ </body>
41
+ </html>
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Заявка | Exam App</title>
7
+ <link rel="stylesheet" href="css/style.css" />
8
+ </head>
9
+ <body>
10
+ <header class="header">
11
+ <div class="logo" data-config="appName">Корочки.есть</div>
12
+ <nav>
13
+ <a href="index.html">Главная</a>
14
+ <a href="request.html">Заявка</a>
15
+ <a href="profile.html">Мои заявки</a>
16
+ <a href="admin.html">Админ</a>
17
+ </nav>
18
+ </header>
19
+
20
+ <main class="page">
21
+ <form class="form-card wide" id="requestForm">
22
+ <h1>Формирование заявки</h1>
23
+
24
+ <label>Курс</label>
25
+ <select id="course"></select>
26
+
27
+ <label>Дата начала обучения</label>
28
+ <input id="date" type="text" placeholder="ДД.ММ.ГГГГ" />
29
+
30
+ <label>Способ оплаты</label>
31
+ <select id="payment"></select>
32
+
33
+ <div class="error" id="error"></div>
34
+
35
+ <button class="btn" type="submit">Отправить заявку</button>
36
+ </form>
37
+ </main>
38
+
39
+ <script src="js/config.js"></script>
40
+ <script src="js/main.js"></script>
41
+ <script src="js/app.js"></script>
42
+ </body>
43
+ </html>
@@ -0,0 +1,16 @@
1
+ const express = require("express");
2
+ const path = require("path");
3
+
4
+ const app = express();
5
+
6
+ app.use(express.static(__dirname));
7
+
8
+ app.get("/", (req, res) => {
9
+ res.sendFile(path.join(__dirname, "index.html"));
10
+ });
11
+
12
+ const PORT = 3000;
13
+
14
+ app.listen(PORT, () => {
15
+ console.log(`Сервер запущен: http://localhost:${PORT}`);
16
+ });