full app with server + data.json database

This commit is contained in:
tore 2026-09-14 11:28:36 +00:00
parent 78be3fc67f
commit cf45d0a038
6 changed files with 144 additions and 13 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
.vibe42-run.log
.vibe42-run.pid

26
data.json Normal file
View File

@ -0,0 +1,26 @@
[
{
"id": 1,
"firstName": "Алишер",
"lastName": "Нурсултанов",
"phone": "+7 (777) 123-45-67",
"telegram": "@alisher_ns",
"registeredAt": "2026-09-12T10:30:00.000Z"
},
{
"id": 2,
"firstName": "Динара",
"lastName": "Кенжегулова",
"phone": "+7 (701) 234-56-78",
"telegram": "@dinara_kz",
"registeredAt": "2026-09-13T14:15:00.000Z"
},
{
"id": 3,
"firstName": "Максат",
"lastName": "Ибрагимов",
"phone": "+7 (778) 345-67-89",
"telegram": "@maxat_dev",
"registeredAt": "2026-09-13T16:45:00.000Z"
}
]

View File

@ -75,6 +75,14 @@
font-size: 48px; font-size: 48px;
margin-bottom: var(--kt-ai-space-3); margin-bottom: var(--kt-ai-space-3);
} }
.error-message {
background: var(--kt-ai-bg-soft);
border: 1px solid var(--kt-ai-border);
border-radius: var(--kt-ai-radius-xl);
padding: var(--kt-ai-space-4);
color: var(--kt-ai-fg-muted);
margin-top: var(--kt-ai-space-4);
}
</style> </style>
</head> </head>
<body class="kt-ai-app" data-kt-data-mode="demo"> <body class="kt-ai-app" data-kt-data-mode="demo">
@ -100,7 +108,7 @@
</p> </p>
<div class="kt-ai-hero-actions" style="justify-content:center"> <div class="kt-ai-hero-actions" style="justify-content:center">
<a class="kt-ai-btn" data-variant="primary" data-size="lg" href="#register">Зарегистрироваться</a> <a class="kt-ai-btn" data-variant="primary" data-size="lg" href="#register">Зарегистрироваться</a>
<a class="kt-ai-btn" data-size="lg" href="#participants">Участники</a> <a class="kt-ai-btn" data-size="lg" href="#about">О хакатоне</a>
</div> </div>
</div> </div>
</div> </div>
@ -175,9 +183,10 @@
<h3 style="margin-bottom:var(--kt-ai-space-2)">Регистрация успешна!</h3> <h3 style="margin-bottom:var(--kt-ai-space-2)">Регистрация успешна!</h3>
<p style="color:var(--kt-ai-fg-muted)">Добро пожаловать на Hackathon Open 2026!</p> <p style="color:var(--kt-ai-fg-muted)">Добро пожаловать на Hackathon Open 2026!</p>
</div> </div>
<div id="errorMessage" class="error-message" style="display:none"></div>
</div> </div>
</div> </div>
<div style="display:flex;flex-direction:column;gap:var(--kt-ai-space-6)"> <div id="about" style="display:flex;flex-direction:column;gap:var(--kt-ai-space-6)">
<article class="kt-ai-tile"> <article class="kt-ai-tile">
<h3 style="margin-bottom:var(--kt-ai-space-2)">📅 Когда</h3> <h3 style="margin-bottom:var(--kt-ai-space-2)">📅 Когда</h3>
<p style="color:var(--kt-ai-fg-muted)">15-17 ноября 2026, старт в пятницу 18:00</p> <p style="color:var(--kt-ai-fg-muted)">15-17 ноября 2026, старт в пятницу 18:00</p>
@ -199,8 +208,6 @@
</div> </div>
</section> </section>
<footer class="kt-ai-wrap kt-ai-pagefoot"> <footer class="kt-ai-wrap kt-ai-pagefoot">
<span>© 2026 Hackathon Open — Казахтелеком</span> <span>© 2026 Hackathon Open — Казахтелеком</span>
<span class="kt-ai-spacer"></span> <span class="kt-ai-spacer"></span>
@ -209,15 +216,14 @@
</footer> </footer>
<script> <script>
document.getElementById('registrationForm').addEventListener('submit', function(e) { document.getElementById('registrationForm').addEventListener('submit', async function(e) {
e.preventDefault(); e.preventDefault();
const formData = { const formData = {
firstName: document.getElementById('firstName').value.trim(), firstName: document.getElementById('firstName').value.trim(),
lastName: document.getElementById('lastName').value.trim(), lastName: document.getElementById('lastName').value.trim(),
phone: document.getElementById('phone').value.trim(), phone: document.getElementById('phone').value.trim(),
telegram: document.getElementById('telegram').value.trim(), telegram: document.getElementById('telegram').value.trim()
registeredAt: new Date().toISOString()
}; };
if (!formData.firstName || !formData.lastName || !formData.phone || !formData.telegram) { if (!formData.firstName || !formData.lastName || !formData.phone || !formData.telegram) {
@ -228,13 +234,28 @@ document.getElementById('registrationForm').addEventListener('submit', function(
return; return;
} }
const submissions = JSON.parse(localStorage.getItem('hackathonSubmissions') || '[]'); const successMsg = document.getElementById('successMessage');
submissions.push(formData); const errorMsg = document.getElementById('errorMessage');
localStorage.setItem('hackathonSubmissions', JSON.stringify(submissions));
document.getElementById('registrationForm').reset(); try {
document.getElementById('registrationForm').style.display = 'none'; const response = await fetch('/api/registrations', {
document.getElementById('successMessage').style.display = 'block'; method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
if (!response.ok) {
throw new Error('Ошибка сервера: ' + response.status);
}
document.getElementById('registrationForm').reset();
document.getElementById('registrationForm').style.display = 'none';
successMsg.style.display = 'block';
errorMsg.style.display = 'none';
} catch (err) {
errorMsg.textContent = 'Ошибка: ' + err.message + '. Попробуйте ещё раз.';
errorMsg.style.display = 'block';
}
}); });
</script> </script>

10
package-lock.json generated Normal file
View File

@ -0,0 +1,10 @@
{
"name": "hackathon-registration",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "hackathon-registration"
}
}
}

6
package.json Normal file
View File

@ -0,0 +1,6 @@
{
"name": "hackathon-registration",
"private": true,
"type": "commonjs",
"scripts": { "start": "node server.js" }
}

65
server.js Normal file
View File

@ -0,0 +1,65 @@
const http = require("http");
const fs = require("fs");
const path = require("path");
const PORT = process.env.PORT || 3000;
const DATA = path.join(__dirname, "data.json");
function readItems() {
try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return []; }
}
function writeItems(items) {
fs.writeFileSync(DATA, JSON.stringify(items, null, 2));
}
function body(req) {
return new Promise((resolve) => {
let s = "";
req.on("data", (c) => (s += c));
req.on("end", () => { try { resolve(JSON.parse(s || "{}")); } catch (_) { resolve({}); } });
});
}
http.createServer(async (req, res) => {
const urlPath = req.url.split("?")[0];
if (req.method === "GET" && urlPath === "/api/registrations") {
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify(readItems()));
}
if (req.method === "POST" && urlPath === "/api/registrations") {
const item = await body(req);
const items = readItems();
const newItem = {
id: Date.now(),
firstName: item.firstName || "",
lastName: item.lastName || "",
phone: item.phone || "",
telegram: item.telegram || "",
registeredAt: new Date().toISOString()
};
items.push(newItem);
writeItems(items);
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify({ ok: true, data: newItem }));
}
if (req.method === "GET" && urlPath === "/api/stats") {
const items = readItems();
const today = new Date().toDateString();
const todayCount = items.filter(p => new Date(p.registeredAt).toDateString() === today).length;
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify({ total: items.length, today: todayCount }));
}
let file = req.url === "/" ? "/index.html" : urlPath;
const full = path.join(__dirname, file);
if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) {
const ext = path.extname(full);
const type = ext === ".css" ? "text/css" : ext === ".js" ? "application/javascript" : "text/html";
res.writeHead(200, { "Content-Type": type + "; charset=utf-8" });
return res.end(fs.readFileSync(full));
}
res.writeHead(404); res.end("Not found");
}).listen(PORT, () => console.log("Сервер запущен на порту " + PORT));