Fix admin organization proof image preview path and implement forgot password feature with rate limiting and check email warning

This commit is contained in:
ilham_gmail
2026-06-11 20:38:28 +07:00
parent cbbd683470
commit b6f150ef86
5 changed files with 219 additions and 49 deletions
+63 -24
View File
@@ -530,11 +530,27 @@ function openAuthModal(mode){
'<div style="font-size:12px;color:#64748b;margin-top:8px">Akun akan berstatus menunggu persetujuan admin setelah pendaftaran berhasil.</div>';
if(switchArea){ switchArea.innerHTML = '<span>Sudah punya akun? <button type="button" id="auth-switch-to-login" class="data-link" style="background:transparent;border:0;padding:0;cursor:pointer;margin-left:4px">Masuk</button></span>'; }
if(submitBtn) submitBtn.textContent = 'Daftar';
} else {
} else if(mode === 'login'){
title.textContent = 'Masuk';
fields.innerHTML = '<div class="auth-field"><label>Email<input name="email" type="email" required></label></div><div class="auth-field"><label>Password<input name="password" type="password" required></label></div>';
fields.innerHTML = '<div class="auth-field"><label><span class="label-title">Email</span><input name="email" type="email" required></label></div>' +
'<div class="auth-field"><label><span class="label-title">Password</span><input name="password" type="password" required></label></div>' +
'<div style="text-align:right;margin-top:-6px;margin-bottom:12px">' +
'<button type="button" id="auth-forgot-password-link" style="background:transparent;border:0;padding:0;color:#3b82f6;cursor:pointer;font-size:13px">Lupa Password?</button>' +
'</div>';
if(switchArea){ switchArea.innerHTML = '<span>Ingin daftar pengelola? <button type="button" id="auth-switch-to-register" class="data-link" style="background:transparent;border:0;padding:0;cursor:pointer;margin-left:4px">Daftar di sini</button></span>'; }
if(submitBtn) submitBtn.textContent = 'Masuk';
} else if(mode === 'forgot_password'){
title.textContent = 'Lupa Password';
fields.innerHTML =
'<div class="auth-field"><label><span class="label-title">Email <span class="required-star">*</span></span><input name="email" type="email" required></label></div>'+
'<div style="display:flex;gap:8px;margin-top:12px;margin-bottom:12px">' +
'<button type="button" id="auth-send-forgot-code-btn" class="btn" style="flex:1;padding:10px;background:#3b82f6;color:white;border:none;border-radius:4px;cursor:pointer;font-size:14px">Kirim Kode Verifikasi</button>' +
'</div>' +
'<div class="auth-field"><label><span class="label-title">Kode Verifikasi <span class="required-star">*</span></span><input name="verify_code" type="text" placeholder="Masukkan 6 digit" maxlength="6" pattern="[0-9]{6}" required></label></div>'+
'<div class="auth-field"><label><span class="label-title">Password Baru <span class="required-star">*</span></span><input name="password" type="password" required></label></div>'+
'<div class="auth-field"><label><span class="label-title">Konfirmasi Password Baru <span class="required-star">*</span></span><input name="password_confirm" type="password" required></label></div>';
if(switchArea){ switchArea.innerHTML = '<span>Kembali ke <button type="button" id="auth-switch-to-login" class="data-link" style="background:transparent;border:0;padding:0;cursor:pointer;margin-left:4px">Masuk</button></span>'; }
if(submitBtn) submitBtn.textContent = 'Reset Password';
}
modal.classList.remove('hidden');
modal.setAttribute('aria-hidden','false');
@@ -542,8 +558,10 @@ function openAuthModal(mode){
try{
const switchToRegister = document.getElementById('auth-switch-to-register');
const switchToLogin = document.getElementById('auth-switch-to-login');
const forgotPasswordLink = document.getElementById('auth-forgot-password-link');
if(switchToRegister) switchToRegister.onclick = function(){ openAuthModal('register'); };
if(switchToLogin) switchToLogin.onclick = function(){ openAuthModal('login'); };
if(forgotPasswordLink) forgotPasswordLink.onclick = function(){ openAuthModal('forgot_password'); };
}catch(e){}
if(mode === 'register'){
const orgProof = document.getElementById('org_proof_input');
@@ -567,15 +585,16 @@ function openAuthModal(mode){
});
}
}
const form = document.getElementById('auth-modal-form');
form.onsubmit = async function(ev){
ev.preventDefault();
// Regular form submission (register or login)
const endpoint = mode === 'register' ? 'src/api/auth/register.php' : 'src/api/auth/login.php';
let endpoint = 'src/api/auth/login.php';
if(mode === 'register') endpoint = 'src/api/auth/register.php';
if(mode === 'forgot_password') endpoint = 'src/api/auth/reset_password.php';
try{
let resp, jr;
if(mode === 'register'){
if(mode === 'register' || mode === 'forgot_password'){
// Validate password length and match
const passwordInput = form.querySelector('input[name="password"]');
const password = passwordInput ? passwordInput.value : '';
@@ -591,16 +610,18 @@ function openAuthModal(mode){
return;
}
// client-side validation for org_proof: size <=5MB and allowed extensions
const fileInput = form.querySelector('input[name="org_proof"]') || document.getElementById('org_proof_input');
if(!fileInput){ showToast('Input bukti organisasi tidak ditemukan', 'error', 4000); return; }
if(!(fileInput.files && fileInput.files.length > 0)){ showToast('Mohon unggah bukti organisasi (foto/pdf).', 'error', 5000); return; }
const f = fileInput.files[0];
const maxSize = 5 * 1024 * 1024;
const allowed = ['jpg','jpeg','png','svg','pdf'];
const ext = (f.name || '').split('.').pop().toLowerCase();
if(f.size > maxSize){ showToast('Ukuran file bukti organisasi maksimal 5MB', 'error', 5000); return; }
if(!allowed.includes(ext)){ showToast('Jenis file tidak diperbolehkan. Hanya .png, .jpg, .jpeg, .svg, atau .pdf.', 'error', 5000); return; }
if(mode === 'register'){
// client-side validation for org_proof: size <=5MB and allowed extensions
const fileInput = form.querySelector('input[name="org_proof"]') || document.getElementById('org_proof_input');
if(!fileInput){ showToast('Input bukti organisasi tidak ditemukan', 'error', 4000); return; }
if(!(fileInput.files && fileInput.files.length > 0)){ showToast('Mohon unggah bukti organisasi (foto/pdf).', 'error', 5000); return; }
const f = fileInput.files[0];
const maxSize = 5 * 1024 * 1024;
const allowed = ['jpg','jpeg','png','svg','pdf'];
const ext = (f.name || '').split('.').pop().toLowerCase();
if(f.size > maxSize){ showToast('Ukuran file bukti organisasi maksimal 5MB', 'error', 5000); return; }
if(!allowed.includes(ext)){ showToast('Jenis file tidak diperbolehkan. Hanya .png, .jpg, .jpeg, .svg, atau .pdf.', 'error', 5000); return; }
}
// Check verify code
const codeInput = form.querySelector('input[name="verify_code"]');
@@ -610,9 +631,17 @@ function openAuthModal(mode){
return;
}
const fd = new FormData(form);
resp = await fetch(endpoint, { method: 'POST', headers: buildHeaders(null), body: fd, credentials: 'same-origin' });
jr = await resp.json().catch(()=>null);
if(mode === 'register'){
const fd = new FormData(form);
resp = await fetch(endpoint, { method: 'POST', headers: buildHeaders(null), body: fd, credentials: 'same-origin' });
jr = await resp.json().catch(()=>null);
} else {
const fd = new FormData(form);
const payload = {};
for(const [k,v] of fd.entries()) payload[k] = v;
resp = await fetch(endpoint, { method: 'POST', headers: buildHeaders(), body: JSON.stringify(payload), credentials: 'same-origin' });
jr = await resp.json().catch(()=>null);
}
} else {
const fd = new FormData(form);
const payload = {};
@@ -621,13 +650,17 @@ function openAuthModal(mode){
jr = await resp.json().catch(()=>null);
}
if(!resp.ok || !jr || jr.success === false){
showToast((jr && (jr.error || jr.status)) ? (jr.error || jr.status) : 'Gagal memproses respons dari server', 'error', 5000);
showToast((jr && (jr.error || jr.status || jr.message)) ? (jr.error || jr.status || jr.message) : 'Gagal memproses respons dari server', 'error', 5000);
return;
}
if(mode === 'register'){
authRegisterState = { code_sent: false, code: null, email: null };
closeAuthModal();
showToast('Pendaftaran berhasil! Akun menunggu persetujuan admin.', 'success', 5000);
} else if(mode === 'forgot_password'){
authRegisterState = { code_sent: false, code: null, email: null };
closeAuthModal();
showToast('Reset password berhasil! Silakan masuk kembali.', 'success', 5000);
} else {
if(jr && jr.token){ syncAuthToken(jr.token); }
if(jr && jr.user){ syncCurrentUser(jr.user); }
@@ -639,9 +672,9 @@ function openAuthModal(mode){
}catch(e){ console.error(e); showToast('Gagal memproses autentikasi', 'error', 5000); }
};
// Setup event listeners for registration "Send Verification Code" button
if(mode === 'register'){
const sendCodeBtn = document.getElementById('auth-send-code-btn');
// Setup event listeners for registration and forgot password "Send Verification Code" buttons
const wireVerificationBtn = function(btnId) {
const sendCodeBtn = document.getElementById(btnId);
if(sendCodeBtn){
sendCodeBtn.onclick = async function(ev){
ev.preventDefault();
@@ -724,7 +757,7 @@ function openAuthModal(mode){
}
}
showToast('Kode verifikasi dikirim ke email Anda!', 'success', 4000);
showToast('Kode verifikasi terkirim ke email Anda! Silakan cek email Anda.', 'success', 6000);
// Cooldown countdown timer
let cooldown = 30;
@@ -749,6 +782,12 @@ function openAuthModal(mode){
}
};
}
};
if(mode === 'register'){
wireVerificationBtn('auth-send-code-btn');
} else if(mode === 'forgot_password'){
wireVerificationBtn('auth-send-forgot-code-btn');
}
}