MIF_E31231302_WEBSITE/resources/views/auth/login.blade.php

974 lines
26 KiB
PHP

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Login Admin - Sistem TBC</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Google Font -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
*{
margin:0;
padding:0;
box-sizing:border-box;
font-family:'Poppins',sans-serif;
}
body{
height:100vh;
display:flex;
justify-content:center;
align-items:center;
background:linear-gradient(135deg,#f5f5f5,#eaeaea);
}
.container{
text-align:center;
}
.judul-utama{
font-size:17px;
font-weight:750;
color:#b30000;
margin-bottom:45px;
letter-spacing:0.8px;
}
.card-login{
width:400px;
padding:40px;
border-radius:20px;
background:rgba(255,255,255,0.95);
box-shadow:0 15px 35px rgba(0,0,0,0.08);
transition:0.3s;
}
.card-login:hover{
transform:translateY(-5px);
}
.card-login h2{
margin-bottom:25px;
font-weight:600;
color:#b30000;
}
.input-group{
position:relative;
margin-bottom:16px;
}
input{
width:100%;
padding:12px 50px 12px 16px;
border-radius:14px;
border:1px solid #e5e5e5;
font-size:13px;
transition:0.3s;
background:#fafafa;
}
input:focus{
border-color:#b30000;
background:#fff;
box-shadow:0 0 0 4px rgba(179,0,0,0.08);
outline:none;
}
.toggle-password{
position:absolute;
right:16px;
top:50%;
transform:translateY(-50%);
background:none;
border:none;
cursor:pointer;
padding:0;
display:flex;
align-items:center;
}
.toggle-password svg{
stroke:#888;
transition:0.3s;
}
.toggle-password:hover svg{
stroke:#b30000;
}
button[type="submit"]{
width:100%;
padding:12px;
border-radius:14px;
border:none;
background:linear-gradient(135deg,#b30000,#8f0000);
color:white;
font-weight:600;
font-size:14px;
cursor:pointer;
transition:0.3s;
margin-top:8px;
}
button[type="submit"]:hover{
transform:translateY(-1px);
box-shadow:0 10px 20px rgba(179,0,0,0.18);
}
.error{
color:#b30000;
font-size:12px;
margin-bottom:15px;
text-align:left;
}
.success-message{
color:#0f9d58;
font-size:12px;
margin-bottom:15px;
text-align:center;
}
.forgot-password{
margin-top:16px;
text-align:right;
}
.forgot-password a{
color:#b30000;
text-decoration:none;
font-size:12px;
font-weight:600;
cursor:pointer;
}
.forgot-password a:hover{
text-decoration:underline;
}
/* ===================================================== */
/* PERBAIKAN FINAL POPUP SWEET ALERT (SAMA DENGAN AKUN) */
/* ===================================================== */
.swal-modern-popup{
width:430px !important;
border-radius:24px !important;
padding:0 !important;
overflow:hidden !important;
background:#ffffff !important;
box-shadow:0 20px 60px rgba(0,0,0,0.14) !important;
}
.swal2-html-container{
margin:0 !important;
padding:0 !important;
}
.swal-modern-header{
padding:24px 24px 18px;
background:linear-gradient(135deg,#c40000,#930000);
text-align:left;
}
.swal-modern-top{
display:flex;
align-items:center;
gap:14px;
}
.swal-modern-icon{
width:56px;
height:56px;
border-radius:16px;
background:rgba(255,255,255,0.14);
display:flex;
align-items:center;
justify-content:center;
flex-shrink:0;
}
.swal-modern-icon i{
width:26px;
height:26px;
color:white;
}
.swal-modern-title{
font-size:22px !important;
font-weight:700 !important;
color:white !important;
margin:0 !important;
padding:0 !important;
text-align:left !important;
}
.swal-modern-subtitle{
margin-top:4px;
font-size:13px;
line-height:1.5;
color:rgba(255,255,255,0.86);
text-align:left;
}
.swal-modern-body{
padding:22px 24px 10px;
}
.swal-label{
display:block;
text-align:left;
font-size:13px;
font-weight:600;
color:#374151;
margin-bottom:8px;
margin-top:14px;
}
.swal-label:first-child{
margin-top:0;
}
.swal2-input{
width:100% !important;
height:50px !important;
margin:0 !important;
border-radius:14px !important;
border:1px solid #e5e7eb !important;
background:#fafafa !important;
padding:0 16px !important;
font-size:14px !important;
color:#111827 !important;
transition:0.25s !important;
box-shadow:none !important;
}
.swal2-input:focus{
background:#ffffff !important;
border-color:#c40000 !important;
box-shadow:0 0 0 4px rgba(196,0,0,0.08) !important;
}
.swal2-input::placeholder{
color:#9ca3af !important;
}
.swal2-actions{
margin:0 !important;
padding:20px 24px 24px !important;
justify-content:flex-end !important;
gap:10px !important;
}
button.swal-btn-confirm{
background:#c40000 !important;
color:white !important;
border:none !important;
border-radius:14px !important;
height:46px !important;
padding:0 22px !important;
font-size:13px !important;
font-weight:600 !important;
box-shadow:none !important;
}
button.swal-btn-confirm:hover{
background:#a70000 !important;
}
button.swal-btn-cancel{
background:#f3f4f6 !important;
color:#374151 !important;
border:none !important;
border-radius:14px !important;
height:46px !important;
padding:0 22px !important;
font-size:13px !important;
font-weight:600 !important;
box-shadow:none !important;
}
.swal2-styled:focus{
box-shadow:none !important;
}
/* ===================================================== */
/* OTP */
/* ===================================================== */
.otp-wrapper{
display:flex;
justify-content:center;
gap:10px;
margin-top:6px;
}
.otp-input{
width:48px !important;
height:54px !important;
border-radius:14px !important;
border:1px solid #e5e7eb !important;
background:#fafafa !important;
text-align:center;
font-size:22px !important;
font-weight:700 !important;
color:#111827 !important;
transition:0.25s !important;
}
.otp-input:focus{
border-color:#c40000 !important;
background:#ffffff !important;
box-shadow:0 0 0 4px rgba(196,0,0,0.08) !important;
}
/* FIX OTP JUMBO DI SWEETALERT */
.swal2-popup .otp-wrapper{
width:100% !important;
display:flex !important;
justify-content:center !important;
align-items:center !important;
gap:10px !important;
margin-top:10px !important;
overflow:hidden !important;
}
.swal2-popup .otp-input{
width:48px !important;
height:54px !important;
padding:0 !important;
margin:0 !important;
border-radius:14px !important;
border:1px solid #e5e7eb !important;
background:#fafafa !important;
text-align:center !important;
font-size:20px !important;
font-weight:700 !important;
line-height:1 !important;
color:#111827 !important;
box-shadow:none !important;
outline:none !important;
}
.swal2-popup .otp-input:focus{
border-color:#c40000 !important;
background:#ffffff !important;
box-shadow:0 0 0 4px rgba(196,0,0,0.08) !important;
}
/* Hilangkan scroll aneh di popup */
.swal2-popup{
overflow:hidden !important;
}
.swal2-html-container{
overflow:hidden !important;
}
/* ===================================================== */
/* PASSWORD WARNING MODERN */
/* ===================================================== */
.password-alert{
margin-top:14px;
background:#fff7f7;
border:1px solid #ffe0e0;
border-radius:16px;
padding:12px 14px;
display:flex;
align-items:flex-start;
gap:12px;
}
.password-alert-icon{
width:34px;
height:34px;
border-radius:10px;
background:#ffeaea;
display:flex;
align-items:center;
justify-content:center;
flex-shrink:0;
}
.password-alert-icon i{
width:16px;
height:16px;
color:#c40000;
}
.password-alert-text{
text-align:left;
flex:1;
}
.password-alert-text h4{
font-size:12px;
font-weight:700;
color:#991b1b;
margin-bottom:2px;
line-height:1.3;
}
.password-alert-text p{
font-size:11px;
line-height:1.5;
color:#7f1d1d;
margin:0;
}
/* ============================= */
/* RESPONSIVE */
/* ============================= */
@media(max-width:600px){
.swal-modern-popup{
width:92% !important;
}
.swal-modern-header{
padding:24px 20px 18px;
}
.swal-modern-body{
padding:22px 20px 24px;
}
.swal2-actions{
padding:0 20px 22px !important;
}
.otp-input{
width:45px !important;
height:52px !important;
font-size:20px !important;
}
}
</style>
</head>
<body>
<div class="container">
<div class="judul-utama">
SISTEM SKRINING DAN MONITORING<br>
PENGOBATAN TBC
</div>
<div class="card-login">
<h2>Login</h2>
@if($errors->any())
<div class="error">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@if(session('error'))
<div class="error">
{{ session('error') }}
</div>
@endif
@if(session('success'))
<div class="success-message">
{{ session('success') }}
</div>
@endif
<form method="POST" action="/login">
@csrf
<div class="input-group">
<input type="text"
name="id_petugas"
placeholder="ID Petugas"
required>
</div>
<div class="input-group">
<input type="password"
name="password"
id="password"
placeholder="Password"
required>
<button type="button"
class="toggle-password"
onclick="togglePassword()">
<svg id="eyeOpen"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke-width="2">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
<circle cx="12" cy="12" r="3"/>
</svg>
<svg id="eyeClose"
style="display:none;"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke-width="2">
<path d="M17.94 17.94A10.94 10.94 0 0 1 12 20c-7 0-11-8-11-8a21.77 21.77 0 0 1 5.06-6.94"/>
<path d="M1 1l22 22"/>
</svg>
</button>
</div>
<button type="submit">
Login
</button>
</form>
<div class="forgot-password">
<a onclick="resetPasswordLogin()">
Lupa Password?
</a>
</div>
</div>
</div>
<script>
lucide.createIcons();
function togglePassword(){
const password = document.getElementById("password");
const eyeOpen = document.getElementById("eyeOpen");
const eyeClose = document.getElementById("eyeClose");
if(password.type === "password"){
password.type = "text";
eyeOpen.style.display = "none";
eyeClose.style.display = "block";
}else{
password.type = "password";
eyeOpen.style.display = "block";
eyeClose.style.display = "none";
}
}
async function resetPasswordLogin(){
const { value: akun } = await Swal.fire({
html:`
<div class="swal-modern-header">
<div class="swal-modern-top">
<div class="swal-modern-icon">
<i data-lucide="shield-check"></i>
</div>
<div>
<div class="swal-modern-title">Reset Password</div>
<div class="swal-modern-subtitle">Verifikasi akun terlebih dahulu</div>
</div>
</div>
</div>
<div class="swal-modern-body">
<label class="swal-label">ID Petugas</label>
<input id="reset-id" class="swal2-input" placeholder="Masukkan ID Petugas">
<label class="swal-label">Email</label>
<input id="reset-email" class="swal2-input" placeholder="Masukkan Email">
</div>
`,
showCancelButton:true,
confirmButtonText:'Lanjut',
cancelButtonText:'Batal',
reverseButtons:true,
customClass:{
popup:'swal-modern-popup',
confirmButton:'swal-btn-confirm',
cancelButton:'swal-btn-cancel'
},
buttonsStyling:false,
didOpen:() => {
lucide.createIcons();
},
preConfirm:() => {
const idPetugas = document.getElementById('reset-id').value;
const email = document.getElementById('reset-email').value;
if(!idPetugas || !email){
Swal.showValidationMessage('ID dan Email wajib diisi');
return false;
}
return { id_petugas:idPetugas, email:email };
}
});
if(!akun) return;
Swal.fire({
title:'Mengirim OTP...',
allowOutsideClick:false,
showConfirmButton:false,
customClass:{ popup:'swal-modern-popup' },
didOpen:() => { Swal.showLoading(); }
});
try {
const kirimOtp = await fetch('/send-otp-json', {
method:'POST',
headers:{
'Content-Type':'application/json',
'X-CSRF-TOKEN':'{{ csrf_token() }}'
},
body:JSON.stringify({
id_petugas:akun.id_petugas,
email:akun.email
})
});
const otpResult = await kirimOtp.json();
if(!otpResult.success){
Swal.fire({
icon:'error',
title:'Gagal',
text:otpResult.message,
confirmButtonText:'OK',
customClass:{
popup:'swal-modern-popup',
confirmButton:'swal-btn-confirm'
},
buttonsStyling:false
});
return;
}
let otpValid = false;
let finalOtp = '';
while(!otpValid){
const otpInput = await Swal.fire({
html:`
<div class="swal-modern-header">
<div class="swal-modern-top">
<div class="swal-modern-icon">
<i data-lucide="mail-check"></i>
</div>
<div>
<div class="swal-modern-title">Verifikasi OTP</div>
<div class="swal-modern-subtitle">Masukkan 6 digit kode OTP</div>
</div>
</div>
</div>
<div class="swal-modern-body">
<div class="otp-wrapper">
<input type="text" maxlength="1" class="otp-input" id="otp1">
<input type="text" maxlength="1" class="otp-input" id="otp2">
<input type="text" maxlength="1" class="otp-input" id="otp3">
<input type="text" maxlength="1" class="otp-input" id="otp4">
<input type="text" maxlength="1" class="otp-input" id="otp5">
<input type="text" maxlength="1" class="otp-input" id="otp6">
</div>
</div>
`,
showCancelButton:true,
confirmButtonText:'Verifikasi',
cancelButtonText:'Batal',
reverseButtons:true,
customClass:{
popup:'swal-modern-popup',
confirmButton:'swal-btn-confirm',
cancelButton:'swal-btn-cancel'
},
buttonsStyling:false,
didOpen:() => {
lucide.createIcons();
const inputs = document.querySelectorAll('.otp-input');
inputs[0].focus();
inputs.forEach((input, index) => {
input.addEventListener('input', (e) => {
e.target.value = e.target.value.replace(/[^0-9]/g,'');
if(e.target.value.length === 1 && inputs[index + 1]){
inputs[index + 1].focus();
}
});
input.addEventListener('keydown', (e) => {
if(e.key === 'Backspace' && !input.value && inputs[index - 1]){
inputs[index - 1].focus();
}
});
});
},
preConfirm:() => {
const kodeOtp = Array.from({length: 6}, (_, i) =>
document.getElementById(`otp${i+1}`).value
).join('');
if(kodeOtp.length < 6){
Swal.showValidationMessage('OTP harus 6 digit');
return false;
}
return kodeOtp;
}
});
if(!otpInput.isConfirmed) return;
const cekOtp = await fetch('/verifikasi-otp-json', {
method:'POST',
headers:{
'Content-Type':'application/json',
'X-CSRF-TOKEN':'{{ csrf_token() }}'
},
body:JSON.stringify({
id_petugas:akun.id_petugas,
email:akun.email,
otp:otpInput.value,
cek_otp:true
})
});
const otpCheck = await cekOtp.json();
if(otpCheck.success){
otpValid = true;
finalOtp = otpInput.value;
} else {
await Swal.fire({
icon:'error',
title:'OTP Salah',
text: otpCheck.message || 'Silakan masukkan OTP kembali.',
confirmButtonText:'Coba Lagi',
customClass:{
popup:'swal-modern-popup',
confirmButton:'swal-btn-confirm'
},
buttonsStyling:false
});
}
}
const { value: passwordBaru } = await Swal.fire({
html:`
<div class="swal-modern-header">
<div class="swal-modern-top">
<div class="swal-modern-icon">
<i data-lucide="lock-keyhole"></i>
</div>
<div>
<div class="swal-modern-title">Password Baru</div>
<div class="swal-modern-subtitle">Buat password baru yang aman</div>
</div>
</div>
</div>
<div class="swal-modern-body">
<label class="swal-label">Password Baru</label>
<input id="new-password" type="password" class="swal2-input" placeholder="Masukkan Password Baru">
<label class="swal-label">Konfirmasi Password</label>
<input id="confirm-password" type="password" class="swal2-input" placeholder="Konfirmasi Password">
<div class="password-alert">
<div class="password-alert-icon">
<i data-lucide="shield-alert"></i>
</div>
<div class="password-alert-text">
<h4>Keamanan Password</h4>
<p>Password baru tidak boleh sama dengan password sebelumnya.</p>
</div>
</div>
</div>
`,
showCancelButton:true,
confirmButtonText:'Simpan',
cancelButtonText:'Batal',
reverseButtons:true,
customClass:{
popup:'swal-modern-popup',
confirmButton:'swal-btn-confirm',
cancelButton:'swal-btn-cancel'
},
buttonsStyling:false,
didOpen:() => {
lucide.createIcons();
},
preConfirm:() => {
const pass = document.getElementById('new-password').value;
const confirm = document.getElementById('confirm-password').value;
if(pass.length < 8){
Swal.showValidationMessage('Password minimal 8 karakter');
return false;
}
if(pass !== confirm){
Swal.showValidationMessage('Konfirmasi password tidak cocok');
return false;
}
return { password:pass };
}
});
if(!passwordBaru) return;
Swal.fire({
title:'Memproses...',
allowOutsideClick:false,
showConfirmButton:false,
customClass:{ popup:'swal-modern-popup' },
didOpen:() => { Swal.showLoading(); }
});
const simpanPassword = await fetch('/verifikasi-otp-json', {
method:'POST',
headers:{
'Content-Type':'application/json',
'X-CSRF-TOKEN':'{{ csrf_token() }}'
},
body:JSON.stringify({
id_petugas:akun.id_petugas,
email:akun.email,
otp:finalOtp,
password:passwordBaru.password
})
});
const resultPassword = await simpanPassword.json();
Swal.fire({
html: `
<div style="
padding:28px 24px 10px;
text-align:center;
">
<div style="
width:68px;
height:68px;
margin:0 auto 18px;
border-radius:20px;
display:flex;
align-items:center;
justify-content:center;
background:${resultPassword.success ? '#ecfdf3' : '#fff1f2'};
">
<i
data-lucide="${resultPassword.success ? 'check-check' : 'shield-alert'}"
style="
width:30px;
height:30px;
color:${resultPassword.success ? '#16a34a' : '#dc2626'};
"
></i>
</div>
<h2 style="
font-size:20px;
font-weight:700;
color:#111827;
margin-bottom:8px;
">
${resultPassword.success ? 'Berhasil' : 'Password Ditolak'}
</h2>
<p style="
font-size:13px;
line-height:1.7;
color:#6b7280;
margin:0;
">
${resultPassword.message}
</p>
</div>
`,
confirmButtonText:'Mengerti',
customClass:{
popup:'swal-modern-popup',
confirmButton:'swal-btn-confirm'
},
buttonsStyling:false,
didOpen:() => {
lucide.createIcons();
}
}).then(() => {
if(resultPassword.success){
window.location.reload();
}
});
} catch (error) {
Swal.fire({
icon:'error',
title:'Error',
text:'Terjadi kesalahan sistem',
confirmButtonText:'OK',
customClass:{
popup:'swal-modern-popup',
confirmButton:'swal-btn-confirm'
},
buttonsStyling:false
});
}
}
</script>
</body>
</html>