Files
d1041231060-WebGIS-PovertyMap/uas/login.php
T
Athallah Ghathfan Aqila 6215b4367a Initial commit — WebGIS UAS GeoMiskin
Sistem WebGIS analisis sebaran penduduk miskin dan jangkauan rumah ibadah.
Fitur: peta interaktif Leaflet.js, CRUD rumah ibadah & penduduk miskin,
auto-kategorisasi haversine, heatmap, export PDF/CSV, dashboard analitik,
manajemen pengguna (superadmin/admin), login sistem.

Teknologi: PHP, MySQL, Leaflet.js, OpenStreetMap, Nominatim Geocoding, mPDF.

Catatan: jalankan `composer install` di folder uas/ untuk install dependencies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:31:33 +07:00

255 lines
12 KiB
PHP

<?php
session_start();
if (!empty($_SESSION['user'])) { header('Location: index.php'); exit; }
include 'koneksi.php';
// ── AJAX: cek username ──────────────────────────────────────────
if (($_POST['action'] ?? '') === 'check_user') {
$u = trim($_POST['username'] ?? '');
$st = mysqli_prepare($conn, "SELECT id, nama FROM users WHERE username = ?");
mysqli_stmt_bind_param($st, 's', $u);
mysqli_stmt_execute($st);
$row = mysqli_fetch_assoc(mysqli_stmt_get_result($st));
echo json_encode($row ? ['ok'=>true,'nama'=>$row['nama']] : ['ok'=>false]);
exit;
}
// ── AJAX: reset password ────────────────────────────────────────
if (($_POST['action'] ?? '') === 'reset_password') {
$u = trim($_POST['username'] ?? '');
$pass = $_POST['new_password'] ?? '';
if (strlen($pass) < 6) { echo json_encode(['ok'=>false,'msg'=>'Password minimal 6 karakter']); exit; }
$hash = password_hash($pass, PASSWORD_DEFAULT);
$st = mysqli_prepare($conn, "UPDATE users SET password=? WHERE username=?");
mysqli_stmt_bind_param($st, 'ss', $hash, $u);
mysqli_stmt_execute($st);
echo json_encode(mysqli_stmt_affected_rows($st) > 0
? ['ok'=>true]
: ['ok'=>false,'msg'=>'Username tidak ditemukan']);
exit;
}
// ── Login biasa ─────────────────────────────────────────────────
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = trim($_POST['username'] ?? '');
$password = $_POST['password'] ?? '';
$stmt = mysqli_prepare($conn, "SELECT id, username, password, nama, role FROM users WHERE username = ?");
mysqli_stmt_bind_param($stmt, 's', $username);
mysqli_stmt_execute($stmt);
$user = mysqli_fetch_assoc(mysqli_stmt_get_result($stmt));
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user'] = $user;
header('Location: index.php');
exit;
}
$error = 'Username atau password salah.';
}
?>
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login — GeoMiskin</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Inter',sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0f172a}
.bg-pattern{position:fixed;inset:0;background-image:radial-gradient(circle at 25% 40%,rgba(59,130,246,.12) 0%,transparent 55%),radial-gradient(circle at 75% 70%,rgba(99,102,241,.1) 0%,transparent 55%);pointer-events:none}
.card{background:#1e293b;border:1px solid #334155;border-radius:20px;padding:40px;width:100%;max-width:400px;box-shadow:0 24px 48px rgba(0,0,0,.6)}
.logo{text-align:center;margin-bottom:32px}
.logo-icon{width:64px;height:64px;background:linear-gradient(135deg,#3b82f6,#6366f1);border-radius:16px;display:inline-flex;align-items:center;justify-content:center;margin-bottom:16px;box-shadow:0 8px 24px rgba(59,130,246,.35)}
.logo-icon svg{width:32px;height:32px;fill:white}
h1{color:#f1f5f9;font-size:22px;font-weight:700;margin-bottom:4px}
p.subtitle{color:#64748b;font-size:13.5px}
.form-group{margin-bottom:18px}
label{display:block;color:#94a3b8;font-size:12.5px;font-weight:600;margin-bottom:7px;letter-spacing:.3px}
input[type=text],input[type=password]{width:100%;background:#0f172a;border:1px solid #334155;border-radius:10px;padding:12px 14px;color:#f1f5f9;font-size:14.5px;font-family:inherit;outline:none;transition:.2s}
input:focus{border-color:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.2)}
input::placeholder{color:#94a3b8}
.btn{width:100%;padding:13px;background:linear-gradient(135deg,#3b82f6,#6366f1);border:none;border-radius:10px;color:#fff;font-size:15px;font-weight:600;font-family:inherit;cursor:pointer;transition:.2s;letter-spacing:.3px;margin-top:4px}
.btn:hover{opacity:.9;box-shadow:0 6px 20px rgba(59,130,246,.4)}
.btn:active{transform:translateY(1px)}
.btn-ghost{width:100%;padding:0;background:none;border:none;color:#94a3b8;font-size:13px;font-family:inherit;cursor:pointer;margin-top:14px;transition:.15s;text-decoration:underline;text-underline-offset:3px}
.btn-ghost:hover{color:#cbd5e1}
.error{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.35);border-radius:10px;padding:11px 14px;color:#fca5a5;font-size:13px;margin-bottom:18px;display:flex;align-items:center;gap:8px}
.hint{text-align:center;color:#475569;font-size:12px;margin-top:22px}
.hint span{color:#64748b;font-weight:500}
/* ── Modal ── */
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.7);display:flex;align-items:center;justify-content:center;z-index:999;opacity:0;pointer-events:none;transition:opacity .2s}
.overlay.show{opacity:1;pointer-events:all}
.modal{background:#1e293b;border:1px solid #334155;border-radius:18px;padding:32px;width:100%;max-width:380px;transform:translateY(16px);transition:transform .2s}
.overlay.show .modal{transform:translateY(0)}
.modal-title{color:#f1f5f9;font-size:17px;font-weight:700;margin-bottom:6px}
.modal-sub{color:#64748b;font-size:13px;margin-bottom:24px}
.alert-ok{background:rgba(34,197,94,.1);border:1px solid rgba(34,197,94,.3);border-radius:10px;padding:11px 14px;color:#86efac;font-size:13px;margin-bottom:16px;display:none}
.alert-err{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.3);border-radius:10px;padding:11px 14px;color:#fca5a5;font-size:13px;margin-bottom:16px;display:none}
.step{display:none}.step.active{display:block}
.back-link{display:inline-flex;align-items:center;gap:4px;color:#64748b;font-size:12.5px;cursor:pointer;border:none;background:none;font-family:inherit;margin-bottom:16px;padding:0}
.back-link:hover{color:#94a3b8}
</style>
</head>
<body>
<div class="bg-pattern"></div>
<!-- ── Form Login ── -->
<div class="card">
<div class="logo">
<div class="logo-icon">
<svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>
</div>
<h1>GeoMiskin</h1>
<p class="subtitle">WebGIS Sebaran Penduduk Miskin</p>
</div>
<?php if ($error): ?>
<div class="error">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>
<?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label>Username</label>
<input type="text" name="username" placeholder="Masukkan username" autocomplete="username" required>
</div>
<div class="form-group">
<label>Password</label>
<input type="password" name="password" placeholder="Masukkan password" autocomplete="current-password" required>
</div>
<button class="btn" type="submit">Masuk ke Sistem</button>
<button class="btn-ghost" type="button" onclick="openReset()">Lupa password?</button>
</form>
<p class="hint">Default: <span>admin</span> / <span>admin123</span></p>
<div style="text-align:center;margin-top:20px;padding-top:20px;border-top:1px solid #1e293b">
<a href="../index.php" style="color:#475569;font-size:12px;text-decoration:none;display:inline-flex;align-items:center;gap:6px;transition:.15s" onmouseover="this.style.color='#94a3b8'" onmouseout="this.style.color='#475569'">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
Kembali ke Portal
</a>
</div>
</div>
<!-- ── Modal Lupa Password ── -->
<div class="overlay" id="overlay" onclick="closeReset(event)">
<div class="modal" onclick="event.stopPropagation()">
<!-- Step 1: cek username -->
<div class="step active" id="step1">
<div class="modal-title">🔑 Lupa Password</div>
<div class="modal-sub">Masukkan username Anda untuk melanjutkan.</div>
<div class="alert-err" id="err1"></div>
<div class="form-group">
<label>Username</label>
<input type="text" id="fp-username" placeholder="Masukkan username" autocomplete="off">
</div>
<button class="btn" onclick="checkUser()">Lanjutkan</button>
</div>
<!-- Step 2: set password baru -->
<div class="step" id="step2">
<button class="back-link" onclick="goStep(1)">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
Kembali
</button>
<div class="modal-title">Password Baru</div>
<div class="modal-sub" id="step2-sub"></div>
<div class="alert-err" id="err2"></div>
<div class="alert-ok" id="ok2"></div>
<div class="form-group">
<label>Password Baru</label>
<input type="password" id="fp-pass1" placeholder="Minimal 6 karakter">
</div>
<div class="form-group">
<label>Konfirmasi Password</label>
<input type="password" id="fp-pass2" placeholder="Ulangi password baru">
</div>
<button class="btn" onclick="doReset()">Simpan Password</button>
</div>
</div>
</div>
<script>
function openReset() {
document.getElementById('fp-username').value = '';
document.getElementById('fp-pass1').value = '';
document.getElementById('fp-pass2').value = '';
hideAlerts();
goStep(1);
document.getElementById('overlay').classList.add('show');
}
function closeReset(e) {
if (!e || e.target === document.getElementById('overlay'))
document.getElementById('overlay').classList.remove('show');
}
function goStep(n) {
document.querySelectorAll('.step').forEach(s => s.classList.remove('active'));
document.getElementById('step'+n).classList.add('active');
}
function hideAlerts() {
['err1','err2','ok2'].forEach(id => {
const el = document.getElementById(id);
el.style.display = 'none';
el.textContent = '';
});
}
async function checkUser() {
const u = document.getElementById('fp-username').value.trim();
if (!u) { showErr('err1', 'Username tidak boleh kosong'); return; }
const fd = new FormData();
fd.append('action', 'check_user');
fd.append('username', u);
const res = await fetch('login.php', {method:'POST', body:fd});
const data = await res.json();
if (data.ok) {
document.getElementById('step2-sub').textContent = 'Halo, ' + data.nama + '. Silakan buat password baru.';
hideAlerts();
goStep(2);
} else {
showErr('err1', 'Username tidak ditemukan');
}
}
async function doReset() {
const u = document.getElementById('fp-username').value.trim();
const p1 = document.getElementById('fp-pass1').value;
const p2 = document.getElementById('fp-pass2').value;
if (p1.length < 6) { showErr('err2', 'Password minimal 6 karakter'); return; }
if (p1 !== p2) { showErr('err2', 'Password tidak cocok'); return; }
const fd = new FormData();
fd.append('action', 'reset_password');
fd.append('username', u);
fd.append('new_password', p1);
const res = await fetch('login.php', {method:'POST', body:fd});
const data = await res.json();
if (data.ok) {
document.getElementById('err2').style.display = 'none';
showOk('ok2', 'Password berhasil diubah! Silakan login kembali.');
document.getElementById('fp-pass1').value = '';
document.getElementById('fp-pass2').value = '';
setTimeout(() => document.getElementById('overlay').classList.remove('show'), 2000);
} else {
showErr('err2', data.msg || 'Gagal mengubah password');
}
}
function showErr(id, msg) {
const el = document.getElementById(id);
el.textContent = msg;
el.style.display = 'block';
}
function showOk(id, msg) {
const el = document.getElementById(id);
el.textContent = msg;
el.style.display = 'block';
}
</script>
</body>
</html>