diff --git a/admin_dashboard.php b/admin_dashboard.php new file mode 100644 index 0000000..50f914d --- /dev/null +++ b/admin_dashboard.php @@ -0,0 +1,137 @@ + + + + + + + Admin Dashboard + + + + + + + + + + + + + + + + +
+
+
+

Report Pendaftar

+

Total pendaftar:

+
+
+ Data terbaru otomatis tersimpan di database. +
+
+ + +
+ +
+ + +
+
+
+
+

Daftar Pendaftar

+ +
Belum ada pendaftar.
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
FotoNamaPendidikanJurusanNomor
+ Foto + + Detail +
+
+ +
+
+
+
+
+
+

Detail Pendaftar

+ +

Pilih pendaftar dari tabel untuk melihat detail.

+ +
+ Foto +
+
+
+
+
+
    +
  • Pendidikan:
  • +
  • Jurusan:
  • +
  • Waktu daftar:
  • +
+ +
+
+
+
+
+ + diff --git a/admin_login.php b/admin_login.php new file mode 100644 index 0000000..30f4e5d --- /dev/null +++ b/admin_login.php @@ -0,0 +1,89 @@ + + + + + + + Login Admin + + + + + + + + + + + + + + +
+
+
+
+
+

Login Admin

+

Gunakan kredensial admin untuk mengakses laporan pendaftar.

+ +
+ +
+ +
+
+ + +
Username wajib diisi.
+
+
+ + +
Password wajib diisi.
+
+ +
+
+ Default demo: admin / admin123 +
+
+
+ +
+
+
+ + diff --git a/assets/css/custom.css b/assets/css/custom.css index 789132e..baccd2f 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,403 +1,113 @@ -body { - background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab); - background-size: 400% 400%; - animation: gradient 15s ease infinite; - color: #212529; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - margin: 0; - min-height: 100vh; +:root { + --bg: #f6f7f9; + --surface: #ffffff; + --border: #e5e7eb; + --text: #111827; + --muted: #6b7280; + --accent: #111827; + --accent-soft: #f3f4f6; + --radius-sm: 6px; + --radius-md: 10px; + --shadow-sm: 0 6px 16px rgba(15, 23, 42, 0.08); } -.main-wrapper { - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - width: 100%; - padding: 20px; - box-sizing: border-box; - position: relative; - z-index: 1; +* { + box-sizing: border-box; } -@keyframes gradient { - 0% { - background-position: 0% 50%; - } - 50% { - background-position: 100% 50%; - } - 100% { - background-position: 0% 50%; - } +body.app-body { + font-family: 'Inter', system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + background: var(--bg); + color: var(--text); + font-size: 15px; } -.chat-container { - width: 100%; - max-width: 600px; - background: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 20px; - display: flex; - flex-direction: column; - height: 85vh; - box-shadow: 0 20px 40px rgba(0,0,0,0.2); - backdrop-filter: blur(15px); - -webkit-backdrop-filter: blur(15px); - overflow: hidden; +.navbar-brand { + letter-spacing: -0.02em; } -.chat-header { - padding: 1.5rem; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - background: rgba(255, 255, 255, 0.5); - font-weight: 700; - font-size: 1.1rem; - display: flex; - justify-content: space-between; - align-items: center; +.card { + border-radius: var(--radius-md); } -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 1.25rem; +.btn { + border-radius: var(--radius-sm); + font-weight: 600; + letter-spacing: 0.01em; } -/* Custom Scrollbar */ -::-webkit-scrollbar { - width: 6px; +.btn-dark { + background: var(--accent); + border-color: var(--accent); } -::-webkit-scrollbar-track { - background: transparent; +.btn-outline-secondary { + border-color: var(--border); + color: var(--text); } -::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 10px; +.btn-outline-secondary:hover { + background: var(--accent-soft); + color: var(--text); } -::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.5); +.photo-preview { + min-height: 140px; + background: var(--accent-soft); } -.message { - max-width: 85%; - padding: 0.85rem 1.1rem; - border-radius: 16px; - line-height: 1.5; - font-size: 0.95rem; - box-shadow: 0 4px 15px rgba(0,0,0,0.05); - animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); +.photo-thumb { + width: 44px; + height: 44px; + object-fit: cover; } -@keyframes fadeIn { - from { opacity: 0; transform: translateY(20px) scale(0.95); } - to { opacity: 1; transform: translateY(0) scale(1); } +.detail-photo { + width: 84px; + height: 84px; + object-fit: cover; } -.message.visitor { - align-self: flex-end; - background: linear-gradient(135deg, #212529 0%, #343a40 100%); - color: #fff; - border-bottom-right-radius: 4px; +.table thead th { + font-weight: 600; + border-bottom: 1px solid var(--border); } -.message.bot { - align-self: flex-start; - background: #ffffff; - color: #212529; - border-bottom-left-radius: 4px; +.table tbody tr { + border-bottom: 1px solid var(--border); } -.chat-input-area { - padding: 1.25rem; - background: rgba(255, 255, 255, 0.5); - border-top: 1px solid rgba(0, 0, 0, 0.05); +.border-bottom, +.border-top { + border-color: var(--border) !important; } -.chat-input-area form { - display: flex; - gap: 0.75rem; +.shadow-sm { + box-shadow: var(--shadow-sm) !important; } -.chat-input-area input { - flex: 1; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - padding: 0.75rem 1rem; - outline: none; - background: rgba(255, 255, 255, 0.9); - transition: all 0.3s ease; +.alert { + border-radius: var(--radius-sm); } -.chat-input-area input:focus { - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2); +.badge { + border-radius: 999px; } -.chat-input-area button { - background: #212529; - color: #fff; - border: none; - padding: 0.75rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - transition: all 0.3s ease; +.form-control, +.form-select { + border-radius: var(--radius-sm); + border-color: var(--border); } -.chat-input-area button:hover { - background: #000; - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(0,0,0,0.2); +.form-control:focus, +.form-select:focus { + border-color: #9ca3af; + box-shadow: 0 0 0 0.15rem rgba(17, 24, 39, 0.12); } -/* Background Animations */ -.bg-animations { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - overflow: hidden; - pointer-events: none; +@media (max-width: 768px) { + .display-6 { + font-size: 1.9rem; + } } - -.blob { - position: absolute; - width: 500px; - height: 500px; - background: rgba(255, 255, 255, 0.2); - border-radius: 50%; - filter: blur(80px); - animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1); -} - -.blob-1 { - top: -10%; - left: -10%; - background: rgba(238, 119, 82, 0.4); -} - -.blob-2 { - bottom: -10%; - right: -10%; - background: rgba(35, 166, 213, 0.4); - animation-delay: -7s; - width: 600px; - height: 600px; -} - -.blob-3 { - top: 40%; - left: 30%; - background: rgba(231, 60, 126, 0.3); - animation-delay: -14s; - width: 450px; - height: 450px; -} - -@keyframes move { - 0% { transform: translate(0, 0) rotate(0deg) scale(1); } - 33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); } - 66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); } - 100% { transform: translate(0, 0) rotate(360deg) scale(1); } -} - -.header-link { - font-size: 14px; - color: #fff; - text-decoration: none; - background: rgba(0, 0, 0, 0.2); - padding: 0.5rem 1rem; - border-radius: 8px; - transition: all 0.3s ease; -} - -.header-link:hover { - background: rgba(0, 0, 0, 0.4); - text-decoration: none; -} - -/* Admin Styles */ -.admin-container { - max-width: 900px; - margin: 3rem auto; - padding: 2.5rem; - background: rgba(255, 255, 255, 0.85); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border-radius: 24px; - box-shadow: 0 20px 50px rgba(0,0,0,0.15); - border: 1px solid rgba(255, 255, 255, 0.4); - position: relative; - z-index: 1; -} - -.admin-container h1 { - margin-top: 0; - color: #212529; - font-weight: 800; -} - -.table { - width: 100%; - border-collapse: separate; - border-spacing: 0 8px; - margin-top: 1.5rem; -} - -.table th { - background: transparent; - border: none; - padding: 1rem; - color: #6c757d; - font-weight: 600; - text-transform: uppercase; - font-size: 0.75rem; - letter-spacing: 1px; -} - -.table td { - background: #fff; - padding: 1rem; - border: none; -} - -.table tr td:first-child { border-radius: 12px 0 0 12px; } -.table tr td:last-child { border-radius: 0 12px 12px 0; } - -.form-group { - margin-bottom: 1.25rem; -} - -.form-group label { - display: block; - margin-bottom: 0.5rem; - font-weight: 600; - font-size: 0.9rem; -} - -.form-control { - width: 100%; - padding: 0.75rem 1rem; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 12px; - background: #fff; - transition: all 0.3s ease; - box-sizing: border-box; -} - -.form-control:focus { - outline: none; - border-color: #23a6d5; - box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1); -} - -.header-container { - display: flex; - justify-content: space-between; - align-items: center; -} - -.header-links { - display: flex; - gap: 1rem; -} - -.admin-card { - background: rgba(255, 255, 255, 0.6); - padding: 2rem; - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.5); - margin-bottom: 2.5rem; - box-shadow: 0 10px 30px rgba(0,0,0,0.05); -} - -.admin-card h3 { - margin-top: 0; - margin-bottom: 1.5rem; - font-weight: 700; -} - -.btn-delete { - background: #dc3545; - color: white; - border: none; - padding: 0.25rem 0.5rem; - border-radius: 4px; - cursor: pointer; -} - -.btn-add { - background: #212529; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 4px; - cursor: pointer; - margin-top: 1rem; -} - -.btn-save { - background: #0088cc; - color: white; - border: none; - padding: 0.8rem 1.5rem; - border-radius: 12px; - cursor: pointer; - font-weight: 600; - width: 100%; - transition: all 0.3s ease; -} - -.webhook-url { - font-size: 0.85em; - color: #555; - margin-top: 0.5rem; -} - -.history-table-container { - overflow-x: auto; - background: rgba(255, 255, 255, 0.4); - padding: 1rem; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.3); -} - -.history-table { - width: 100%; -} - -.history-table-time { - width: 15%; - white-space: nowrap; - font-size: 0.85em; - color: #555; -} - -.history-table-user { - width: 35%; - background: rgba(255, 255, 255, 0.3); - border-radius: 8px; - padding: 8px; -} - -.history-table-ai { - width: 50%; - background: rgba(255, 255, 255, 0.5); - border-radius: 8px; - padding: 8px; -} - -.no-messages { - text-align: center; - color: #777; -} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index d349598..1cd194d 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,39 +1,44 @@ document.addEventListener('DOMContentLoaded', () => { - const chatForm = document.getElementById('chat-form'); - const chatInput = document.getElementById('chat-input'); - const chatMessages = document.getElementById('chat-messages'); + const forms = document.querySelectorAll('.needs-validation'); + forms.forEach((form) => { + form.addEventListener('submit', (event) => { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + }); - const appendMessage = (text, sender) => { - const msgDiv = document.createElement('div'); - msgDiv.classList.add('message', sender); - msgDiv.textContent = text; - chatMessages.appendChild(msgDiv); - chatMessages.scrollTop = chatMessages.scrollHeight; - }; + const profileInput = document.getElementById('profile'); + const previewImg = document.getElementById('photoPreview'); + const placeholder = document.getElementById('photoPlaceholder'); - chatForm.addEventListener('submit', async (e) => { - e.preventDefault(); - const message = chatInput.value.trim(); - if (!message) return; - - appendMessage(message, 'visitor'); - chatInput.value = ''; - - try { - const response = await fetch('api/chat.php', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message }) - }); - const data = await response.json(); - - // Artificial delay for realism - setTimeout(() => { - appendMessage(data.reply, 'bot'); - }, 500); - } catch (error) { - console.error('Error:', error); - appendMessage("Sorry, something went wrong. Please try again.", 'bot'); - } + if (profileInput && previewImg && placeholder) { + profileInput.addEventListener('change', () => { + const file = profileInput.files && profileInput.files[0]; + if (!file) { + previewImg.classList.add('d-none'); + placeholder.classList.remove('d-none'); + return; + } + const reader = new FileReader(); + reader.onload = (e) => { + previewImg.src = e.target.result; + previewImg.classList.remove('d-none'); + placeholder.classList.add('d-none'); + }; + reader.readAsDataURL(file); }); + } + + document.querySelectorAll('a[href^="#"]').forEach((link) => { + link.addEventListener('click', (e) => { + const target = document.querySelector(link.getAttribute('href')); + if (target) { + e.preventDefault(); + target.scrollIntoView({ behavior: 'smooth' }); + } + }); + }); }); diff --git a/assets/uploads/profile_69b61efa4e9b82.18136095.jpg b/assets/uploads/profile_69b61efa4e9b82.18136095.jpg new file mode 100644 index 0000000..75982db Binary files /dev/null and b/assets/uploads/profile_69b61efa4e9b82.18136095.jpg differ diff --git a/assets/uploads/profile_69b61efca2aad8.61938921.jpg b/assets/uploads/profile_69b61efca2aad8.61938921.jpg new file mode 100644 index 0000000..75982db Binary files /dev/null and b/assets/uploads/profile_69b61efca2aad8.61938921.jpg differ diff --git a/export_pdf.php b/export_pdf.php new file mode 100644 index 0000000..3f0e2d5 --- /dev/null +++ b/export_pdf.php @@ -0,0 +1,78 @@ +>"; + $objects[] = "<< /Type /Pages /Kids [3 0 R] /Count 1 >>"; + $objects[] = "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>"; + $objects[] = "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>"; + $objects[] = "<< /Length " . strlen($content) . " >>\nstream\n" . $content . "\nendstream"; + + $pdf = "%PDF-1.4\n"; + $offsets = [0]; + foreach ($objects as $i => $obj) { + $offsets[] = strlen($pdf); + $pdf .= ($i + 1) . " 0 obj\n" . $obj . "\nendobj\n"; + } + $xref = strlen($pdf); + $pdf .= "xref\n0 " . (count($objects) + 1) . "\n"; + $pdf .= "0000000000 65535 f \n"; + for ($i = 1; $i <= count($objects); $i++) { + $pdf .= sprintf("%010d 00000 n \n", $offsets[$i]); + } + $pdf .= "trailer\n<< /Size " . (count($objects) + 1) . " /Root 1 0 R >>\n"; + $pdf .= "startxref\n$xref\n%%EOF"; + return $pdf; +} + +$lines = [ + 'Laporan Pendaftar', + 'Tanggal: ' . date('Y-m-d H:i:s') . ' UTC', + 'Total: ' . count($registrations), + '------------------------------------------------------------', +]; + +foreach ($registrations as $row) { + $lines[] = sprintf( + '%s | %s | %s | %s', + $row['reg_code'], + $row['name'], + $row['education'], + $row['major'] + ); +} + +$pdf = build_pdf($lines); + +header('Content-Type: application/pdf'); +header('Content-Disposition: attachment; filename="report_pendaftar.pdf"'); +header('Content-Length: ' . strlen($pdf)); +echo $pdf; +exit; diff --git a/includes/auth.php b/includes/auth.php new file mode 100644 index 0000000..369a5f5 --- /dev/null +++ b/includes/auth.php @@ -0,0 +1,38 @@ + $type, 'message' => $message]; +} + +function flash_get(): ?array { + if (!empty($_SESSION['flash'])) { + $flash = $_SESSION['flash']; + unset($_SESSION['flash']); + return $flash; + } + return null; +} diff --git a/includes/registration_db.php b/includes/registration_db.php new file mode 100644 index 0000000..c100540 --- /dev/null +++ b/includes/registration_db.php @@ -0,0 +1,50 @@ +exec($sql); +} + +function generate_reg_code(): string { + return 'REG-' . date('Ymd') . '-' . strtoupper(bin2hex(random_bytes(3))); +} + +function insert_registration(array $data): int { + $stmt = db()->prepare( + "INSERT INTO registrations (reg_code, name, education, major, photo_path) + VALUES (:reg_code, :name, :education, :major, :photo_path)" + ); + $stmt->bindValue(':reg_code', $data['reg_code']); + $stmt->bindValue(':name', $data['name']); + $stmt->bindValue(':education', $data['education']); + $stmt->bindValue(':major', $data['major']); + $stmt->bindValue(':photo_path', $data['photo_path']); + $stmt->execute(); + return (int)db()->lastInsertId(); +} + +function fetch_registrations(): array { + $stmt = db()->query("SELECT * FROM registrations ORDER BY created_at DESC"); + return $stmt->fetchAll(); +} + +function fetch_registration(int $id): ?array { + $stmt = db()->prepare("SELECT * FROM registrations WHERE id = :id"); + $stmt->bindValue(':id', $id, PDO::PARAM_INT); + $stmt->execute(); + $row = $stmt->fetch(); + return $row ?: null; +} diff --git a/index.php b/index.php index 7205f3d..8e19bf1 100644 --- a/index.php +++ b/index.php @@ -4,147 +4,184 @@ declare(strict_types=1); @error_reporting(E_ALL); @date_default_timezone_set('UTC'); -$phpVersion = PHP_VERSION; +require_once __DIR__ . '/includes/registration_db.php'; +require_once __DIR__ . '/includes/flash.php'; +ensure_registration_table(); + +$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? ''; +$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; $now = date('Y-m-d H:i:s'); +$flash = flash_get(); ?> - + - New Style - - - - - - - - - - - - - - - + Aplikasi Pendaftaran — Form Online + + + + + + + + + - - + + + + + - -
-
-

Analyzing your requirements and generating your website…

-
- Loading… + + + +
+
+
+
+ Responsive · Mobile & Desktop +

Aplikasi Pendaftaran Online yang rapi, cepat, dan mudah diakses.

+

+ Pendaftar mengisi data tanpa login, sistem memberi nomor pendaftaran otomatis, + dan admin dapat melihat laporan beserta foto serta export PDF. +

+ +
+
+
+
+

Ringkasan Fitur

+
    +
  • ✔ Form pendaftaran tanpa login
  • +
  • ✔ Upload foto dari kamera atau galeri
  • +
  • ✔ Admin report + export PDF
  • +
  • ✔ Tampilan responsif
  • +
+

Update terakhir: UTC

+
+
+
+
+
+
+ +
+
+
+
+

Form Pendaftaran

+

Lengkapi data di bawah ini. Semua field wajib diisi.

+
+ Setelah submit, Anda akan menerima nomor pendaftaran sebagai bukti. +
+
+
+
+
+ +
+ +
+ +
+
+ + +
Nama wajib diisi.
+
+
+ + +
Pilih pendidikan terakhir.
+
+
+ + +
Jurusan wajib diisi.
+
+
+ + +
Gunakan kamera perangkat atau pilih dari galeri.
+
Foto profil wajib diunggah.
+
+
+
+ Preview foto pendaftar + Preview foto akan tampil di sini. +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+

1. Isi Form

+

Pendaftar mengisi data dan unggah foto.

+
+
+
+
+
+
+

2. Nomor Pendaftaran

+

Sistem memberi kode registrasi otomatis.

+
+
+
+
+
+
+

3. Admin Report

+

Admin melihat data + export PDF.

+
+
+
+
+
-