461 lines
21 KiB
PHP
461 lines
21 KiB
PHP
<?php
|
|
session_start();
|
|
include 'koneksi.php';
|
|
|
|
$id_account = $_GET['id_account'] ?? null;
|
|
if (!$id_account && !isset($_SESSION['temp_register'])) {
|
|
header("Location: register.php");
|
|
exit;
|
|
}
|
|
|
|
$error = '';
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$pin = $_POST['pin'] ?? '';
|
|
$confirm_pin = $_POST['confirm_pin'] ?? '';
|
|
|
|
if (empty($pin) || empty($confirm_pin)) {
|
|
$error = "All fields are required.";
|
|
} elseif ($pin !== $confirm_pin) {
|
|
$error = "PIN and Confirm PIN do not match.";
|
|
} elseif (!preg_match('/^\d{6}$/', $pin)) {
|
|
$error = "PIN must be exactly 6 digits.";
|
|
} else {
|
|
try {
|
|
if ($id_account) {
|
|
$stmt = $pdo->prepare("UPDATE account SET pin = ? WHERE id_account = ?");
|
|
if ($stmt->execute([$pin, $id_account])) {
|
|
header("Location: finger.php?id_account=$id_account");
|
|
exit;
|
|
} else {
|
|
$error = "Failed to save PIN.";
|
|
}
|
|
} else {
|
|
$temp = $_SESSION['temp_register'];
|
|
$stmt = $pdo->prepare("INSERT INTO account (name, username, password, password_enc, code_register, status, foto, pin) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
|
if ($stmt->execute([
|
|
$temp['name'],
|
|
$temp['username'],
|
|
$temp['password'],
|
|
$temp['password_enc'],
|
|
$temp['code_register'],
|
|
$temp['status'],
|
|
$temp['foto'],
|
|
$pin
|
|
])) {
|
|
$new_id = $pdo->lastInsertId();
|
|
|
|
include_once 'notification_helper.php';
|
|
send_notification($pdo, 'mdi-account-arrow-right', 'New Account Registration', "User {$temp['name']} ({$temp['username']}) registered independently.", "Current status: rejected. PIN verification not completed. Please check in the Account menu.", false);
|
|
|
|
require_once 'PushHelper.php';
|
|
PushHelper::broadcastToAdmins($pdo, 'New Account Registration', "Account {$temp['name']} ({$temp['username']}) has registered independently.", '/operations_account.php');
|
|
|
|
unset($_SESSION['temp_register']);
|
|
header("Location: finger.php?id_account=$new_id");
|
|
exit;
|
|
} else {
|
|
$error = "Failed to save registration data and PIN.";
|
|
}
|
|
}
|
|
} catch (PDOException $e) {
|
|
$error = "Database Error: " . $e->getMessage();
|
|
}
|
|
}
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
<title>Setup PIN - Identia</title>
|
|
<link rel="stylesheet" href="vendors/feather/feather.css">
|
|
<link rel="stylesheet" href="vendors/ti-icons/css/themify-icons.css">
|
|
<link rel="stylesheet" href="vendors/css/vendor.bundle.base.css">
|
|
<link rel="stylesheet" href="css/vertical-layout-light/style.css">
|
|
<link rel="shortcut icon" href="images/myn.png" />
|
|
<style>
|
|
.pin-input-group {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
margin-bottom: 5px;
|
|
}
|
|
.pin-box {
|
|
width: 45px;
|
|
height: 55px;
|
|
text-align: center;
|
|
font-size: 24px;
|
|
border: none;
|
|
border-bottom: 2px solid #ccc;
|
|
background: transparent;
|
|
outline: none;
|
|
transition: border-color 0.3s;
|
|
}
|
|
.pin-box:focus {
|
|
border-bottom-color: #4B49AC;
|
|
}
|
|
.text-separator {
|
|
text-align: center;
|
|
color: #888;
|
|
font-size: 14px;
|
|
margin: 15px 0;
|
|
}
|
|
|
|
.custom-alert {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: #fff;
|
|
border-left: 4px solid #fcd34d;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
padding: 15px 20px;
|
|
border-radius: 4px;
|
|
z-index: 9999;
|
|
display: none;
|
|
align-items: center;
|
|
gap: 12px;
|
|
min-width: 300px;
|
|
max-width: 400px;
|
|
animation: slideIn 0.3s ease-out;
|
|
}
|
|
.custom-alert.error { border-left-color: #ef4444; }
|
|
.custom-alert.success { border-left-color: #10b981; }
|
|
@keyframes slideIn {
|
|
from { transform: translateX(100%); opacity: 0; }
|
|
to { transform: translateX(0); opacity: 1; }
|
|
}
|
|
@keyframes fadeOut {
|
|
from { opacity: 1; }
|
|
to { opacity: 0; }
|
|
}
|
|
.custom-alert-icon { font-size: 24px; }
|
|
.custom-alert.error .custom-alert-icon { color: #ef4444; }
|
|
.custom-alert.success .custom-alert-icon { color: #10b981; }
|
|
.custom-alert.warning .custom-alert-icon { color: #f59e0b; }
|
|
|
|
.custom-alert-content { flex: 1; text-align: left; }
|
|
.custom-alert-title { font-weight: bold; font-size: 14px; margin-bottom: 2px; color: #333; }
|
|
.custom-alert-msg { font-size: 13px; color: #666; line-height: 1.4; }
|
|
.custom-alert-close { cursor: pointer; color: #999; font-size: 18px; }
|
|
.custom-alert-close:hover { color: #333; }
|
|
|
|
body, .container-scroller, .page-body-wrapper, .content-wrapper {
|
|
background-color: #f8f9fa !important;
|
|
}
|
|
|
|
#rightSideBg { padding: 6rem 5rem; min-height: 100vh; }
|
|
@media (max-width: 991px) {
|
|
#rightSideBg { display: none !important; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container-fluid p-0">
|
|
<div class="row m-0 w-100" style="min-height: 100vh;">
|
|
|
|
<!-- Left Side: Form -->
|
|
<div class="col-12 col-lg-6 d-flex flex-column bg-white" style="z-index: 10; box-shadow: 2px 0 15px rgba(0,0,0,0.1); padding: 4rem 10%; overflow-y: auto; max-height: 100vh;">
|
|
<div class="auth-form-light text-left py-0 px-0 rounded" style="margin: auto 0;">
|
|
<div class="d-flex align-items-center mb-4">
|
|
<img src="images/myn.png" alt="logo" style="height: 48px; filter: brightness(0); margin-right: 15px;">
|
|
<div>
|
|
<h2 class="font-weight-bold text-dark mb-1" style="font-size: 2rem; line-height: 1.2;">Setup PIN</h2>
|
|
<p class="font-weight-light text-muted mb-0" style="font-size: 1rem;">Create a 6-digit PIN for your account security.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<form class="pt-3" method="POST" id="pinForm">
|
|
<!-- Hidden inputs to store the actual compiled PINs -->
|
|
<input type="hidden" name="pin" id="realPin" required>
|
|
<input type="hidden" name="confirm_pin" id="realConfirmPin" required>
|
|
|
|
<div class="form-group text-center">
|
|
<div class="pin-input-group" id="pinGroup1">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="text-separator">Type it again to confirm.</div>
|
|
|
|
<div class="form-group text-center">
|
|
<div class="pin-input-group" id="pinGroup2">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
<input type="password" class="pin-box" maxlength="1" pattern="\d">
|
|
</div>
|
|
</div>
|
|
<div class="mt-3">
|
|
<button type="submit" class="btn btn-block btn-lg text-white font-weight-medium mb-3" style="background-color: #4B49AC; border-radius: 8px; padding: 0.8rem;">Set pin</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<!-- Right Side: Highly Innovative Tech Background -->
|
|
<div id="rightSideBg" class="col-12 col-lg-6 d-flex flex-column align-items-center justify-content-center position-relative" style="background-color: #4B49AC; overflow: hidden;">
|
|
|
|
<!-- Animated Tech Rings -->
|
|
<div style="position: absolute; top: -20%; right: -20%; width: 800px; height: 800px; border: 1px dashed rgba(255,255,255,0.15); border-radius: 50%; animation: spinSlow 60s linear infinite; z-index: 0; pointer-events: none;">
|
|
<div style="position: absolute; top: 10%; left: 10%; width: 80%; height: 80%; border: 1px solid rgba(255,255,255,0.05); border-radius: 50%; animation: spinReverse 40s linear infinite;">
|
|
<div style="position: absolute; top: -5px; left: 50%; width: 10px; height: 10px; background: rgba(255,255,255,0.8); border-radius: 50%; box-shadow: 0 0 15px rgba(255,255,255,1);"></div>
|
|
</div>
|
|
<div style="position: absolute; top: 25%; left: 25%; width: 50%; height: 50%; border: 2px solid rgba(255,255,255,0.03); border-radius: 50%;"></div>
|
|
</div>
|
|
|
|
<style>
|
|
@keyframes spinSlow { 100% { transform: rotate(360deg); } }
|
|
@keyframes spinReverse { 100% { transform: rotate(-360deg); } }
|
|
</style>
|
|
|
|
<!-- Interactive Particle Canvas -->
|
|
<canvas id="particleCanvas" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; pointer-events: auto;"></canvas>
|
|
|
|
<!-- Script for Interactive Particles -->
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
const canvas = document.getElementById("particleCanvas");
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext("2d");
|
|
let particlesArray;
|
|
|
|
function resize() {
|
|
canvas.width = canvas.parentElement.clientWidth;
|
|
canvas.height = canvas.parentElement.clientHeight;
|
|
}
|
|
window.addEventListener('resize', resize);
|
|
resize();
|
|
|
|
let mouse = { x: null, y: null, radius: 120 };
|
|
canvas.addEventListener('mousemove', function(event) {
|
|
const rect = canvas.getBoundingClientRect();
|
|
mouse.x = event.clientX - rect.left;
|
|
mouse.y = event.clientY - rect.top;
|
|
});
|
|
canvas.addEventListener('mouseleave', function() {
|
|
mouse.x = null;
|
|
mouse.y = null;
|
|
});
|
|
|
|
class Particle {
|
|
constructor(x, y, directionX, directionY, size, color) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.directionX = directionX;
|
|
this.directionY = directionY;
|
|
this.size = size;
|
|
this.color = color;
|
|
}
|
|
draw() {
|
|
ctx.beginPath();
|
|
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, false);
|
|
ctx.fillStyle = this.color;
|
|
ctx.fill();
|
|
}
|
|
update() {
|
|
if (this.x > canvas.width || this.x < 0) this.directionX = -this.directionX;
|
|
if (this.y > canvas.height || this.y < 0) this.directionY = -this.directionY;
|
|
|
|
if (mouse.x != null && mouse.y != null) {
|
|
let dx = mouse.x - this.x;
|
|
let dy = mouse.y - this.y;
|
|
let distance = Math.sqrt(dx * dx + dy * dy);
|
|
if (distance < mouse.radius + this.size) {
|
|
if (mouse.x < this.x && this.x < canvas.width - this.size * 10) this.x += 1;
|
|
if (mouse.x > this.x && this.x > this.size * 10) this.x -= 1;
|
|
if (mouse.y < this.y && this.y < canvas.height - this.size * 10) this.y += 1;
|
|
if (mouse.y > this.y && this.y > this.size * 10) this.y -= 1;
|
|
}
|
|
}
|
|
this.x += this.directionX;
|
|
this.y += this.directionY;
|
|
this.draw();
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
particlesArray = [];
|
|
let numberOfParticles = (canvas.height * canvas.width) / 10000;
|
|
for (let i = 0; i < numberOfParticles; i++) {
|
|
let size = (Math.random() * 2) + 0.5;
|
|
let x = (Math.random() * ((canvas.width - size * 2) - (size * 2)) + size * 2);
|
|
let y = (Math.random() * ((canvas.height - size * 2) - (size * 2)) + size * 2);
|
|
let directionX = (Math.random() * 1) - 0.5;
|
|
let directionY = (Math.random() * 1) - 0.5;
|
|
let color = 'rgba(255,255,255,0.4)';
|
|
particlesArray.push(new Particle(x, y, directionX, directionY, size, color));
|
|
}
|
|
}
|
|
|
|
function connect() {
|
|
for (let a = 0; a < particlesArray.length; a++) {
|
|
for (let b = a; b < particlesArray.length; b++) {
|
|
let distance = ((particlesArray[a].x - particlesArray[b].x) * (particlesArray[a].x - particlesArray[b].x)) +
|
|
((particlesArray[a].y - particlesArray[b].y) * (particlesArray[a].y - particlesArray[b].y));
|
|
if (distance < (canvas.width / 7) * (canvas.height / 7)) {
|
|
let opacityValue = 1 - (distance / 20000);
|
|
ctx.strokeStyle = 'rgba(255,255,255,' + opacityValue * 0.4 + ')';
|
|
ctx.lineWidth = 1;
|
|
ctx.beginPath();
|
|
ctx.moveTo(particlesArray[a].x, particlesArray[a].y);
|
|
ctx.lineTo(particlesArray[b].x, particlesArray[b].y);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function animate() {
|
|
requestAnimationFrame(animate);
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
for (let i = 0; i < particlesArray.length; i++) {
|
|
particlesArray[i].update();
|
|
}
|
|
connect();
|
|
}
|
|
|
|
init();
|
|
animate();
|
|
});
|
|
</script>
|
|
|
|
<!-- Sliding Text Container -->
|
|
<div style="z-index: 2; max-width: 600px; color: #fff; pointer-events: none;">
|
|
<div id="loginTextSlider" class="carousel slide" data-ride="carousel" data-interval="4000" style="pointer-events: auto;">
|
|
<div class="carousel-inner">
|
|
<div class="carousel-item active">
|
|
<h1 class="font-weight-bold mb-4" style="font-size: 4rem; letter-spacing: 0px; text-shadow: 0 4px 10px rgba(0,0,0,0.1);">Welcome to...</h1>
|
|
<p style="font-size: 1.15rem; opacity: 0.85; font-weight: 300; line-height: 1.6;">Experience the best way to manage your work and track user activities efficiently with our state-of-the-art platform.</p>
|
|
</div>
|
|
<div class="carousel-item">
|
|
<h1 class="font-weight-bold mb-4" style="font-size: 4rem; letter-spacing: 0px; text-shadow: 0 4px 10px rgba(0,0,0,0.1);">Secure Access</h1>
|
|
<p style="font-size: 1.15rem; opacity: 0.85; font-weight: 300; line-height: 1.6;">Your data is protected with military-grade security encryption and seamless advanced biometric login features.</p>
|
|
</div>
|
|
<div class="carousel-item">
|
|
<h1 class="font-weight-bold mb-4" style="font-size: 4rem; letter-spacing: 0px; text-shadow: 0 4px 10px rgba(0,0,0,0.1);">Deep Analytics</h1>
|
|
<p style="font-size: 1.15rem; opacity: 0.85; font-weight: 300; line-height: 1.6;">Get detailed insights, real-time history logs, and actionable metrics to make informed administrative decisions instantly.</p>
|
|
</div>
|
|
</div>
|
|
<!-- Indicators aligned to the left -->
|
|
<ol class="carousel-indicators" style="position: relative; margin-top: 3rem; margin-bottom: 0; justify-content: flex-start; margin-left: 0;">
|
|
<li data-target="#loginTextSlider" data-slide-to="0" class="active" style="width: 10px; height: 10px; border-radius: 50%; margin: 0 8px 0 0; background-color: #fff; opacity: 0.5; border: none;"></li>
|
|
<li data-target="#loginTextSlider" data-slide-to="1" style="width: 10px; height: 10px; border-radius: 50%; margin: 0 8px 0 0; background-color: #fff; opacity: 0.5; border: none;"></li>
|
|
<li data-target="#loginTextSlider" data-slide-to="2" style="width: 10px; height: 10px; border-radius: 50%; margin: 0 8px 0 0; background-color: #fff; opacity: 0.5; border: none;"></li>
|
|
</ol>
|
|
<style>
|
|
.carousel-indicators .active { opacity: 1 !important; transform: scale(1.2); }
|
|
</style>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Custom Alert Container -->
|
|
<div id="customAlert" class="custom-alert warning">
|
|
<i class="mdi mdi-alert-circle custom-alert-icon" id="customAlertIcon"></i>
|
|
<div class="custom-alert-content">
|
|
<div class="custom-alert-title" id="customAlertTitle">Warning</div>
|
|
<div class="custom-alert-msg" id="customAlertMsg">Message goes here</div>
|
|
</div>
|
|
<i class="mdi mdi-close custom-alert-close" onclick="closeCustomAlert()"></i>
|
|
</div>
|
|
|
|
<script src="vendors/js/vendor.bundle.base.js"></script>
|
|
<script>
|
|
function showCustomAlert(title, message, type='warning') {
|
|
const alertBox = document.getElementById('customAlert');
|
|
const icon = document.getElementById('customAlertIcon');
|
|
|
|
alertBox.className = 'custom-alert ' + type;
|
|
if(type === 'error') icon.className = 'mdi mdi-close-circle custom-alert-icon';
|
|
else if(type === 'success') icon.className = 'mdi mdi-check-circle custom-alert-icon';
|
|
else icon.className = 'mdi mdi-alert-circle custom-alert-icon';
|
|
|
|
document.getElementById('customAlertTitle').innerText = title;
|
|
document.getElementById('customAlertMsg').innerText = message;
|
|
|
|
alertBox.style.display = 'flex';
|
|
alertBox.style.animation = 'slideIn 0.3s ease-out';
|
|
|
|
setTimeout(() => closeCustomAlert(), 5000); // auto close after 5s
|
|
}
|
|
|
|
function closeCustomAlert() {
|
|
const alertBox = document.getElementById('customAlert');
|
|
alertBox.style.animation = 'fadeOut 0.3s ease-out forwards';
|
|
setTimeout(() => {
|
|
alertBox.style.display = 'none';
|
|
}, 300);
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const setupPinGroup = (groupId, hiddenInputId) => {
|
|
const group = document.getElementById(groupId);
|
|
const inputs = group.querySelectorAll('.pin-box');
|
|
const hiddenInput = document.getElementById(hiddenInputId);
|
|
|
|
inputs.forEach((input, index) => {
|
|
input.addEventListener('input', (e) => {
|
|
// Only allow numbers
|
|
input.value = input.value.replace(/\D/g, '');
|
|
|
|
if (input.value !== '' && index < inputs.length - 1) {
|
|
inputs[index + 1].focus();
|
|
}
|
|
updateHidden();
|
|
});
|
|
|
|
input.addEventListener('keydown', (e) => {
|
|
// Backspace handling
|
|
if (e.key === 'Backspace' && input.value === '' && index > 0) {
|
|
inputs[index - 1].focus();
|
|
}
|
|
});
|
|
});
|
|
|
|
function updateHidden() {
|
|
let pin = '';
|
|
inputs.forEach(inp => pin += inp.value);
|
|
hiddenInput.value = pin;
|
|
}
|
|
};
|
|
|
|
setupPinGroup('pinGroup1', 'realPin');
|
|
setupPinGroup('pinGroup2', 'realConfirmPin');
|
|
|
|
document.getElementById('pinForm').addEventListener('submit', (e) => {
|
|
const p1 = document.getElementById('realPin').value;
|
|
const p2 = document.getElementById('realConfirmPin').value;
|
|
if (p1.length < 6 || p2.length < 6) {
|
|
e.preventDefault();
|
|
showCustomAlert("Incomplete Form", "Please fill in all PIN slots (6 digits).", "warning");
|
|
}
|
|
});
|
|
|
|
// Tampilkan error PHP jika ada menggunakan custom alert
|
|
<?php if($error): ?>
|
|
showCustomAlert("PIN Setup Failed", "<?= addslashes(htmlspecialchars($error)) ?>", "error");
|
|
<?php endif; ?>
|
|
});
|
|
|
|
// Enforce Notification Permission
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if ('Notification' in window && Notification.permission !== 'granted') {
|
|
Notification.requestPermission();
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|