158 lines
5.2 KiB
PHP
158 lines
5.2 KiB
PHP
{{-- resources/views/auth/otp-verify.blade.php --}}
|
|
@extends('layouts.auth')
|
|
|
|
@section('title', 'Verifikasi OTP')
|
|
|
|
@section('content')
|
|
|
|
{{-- Steps indicator --}}
|
|
<div class="steps">
|
|
<div class="step done"><div class="step-dot">✓</div> Email</div>
|
|
<div class="step-line done"></div>
|
|
<div class="step active"><div class="step-dot">2</div> Kode OTP</div>
|
|
<div class="step-line"></div>
|
|
<div class="step"><div class="step-dot">3</div> Password Baru</div>
|
|
</div>
|
|
|
|
<div class="page-title">Masukkan Kode OTP</div>
|
|
<p class="page-sub">
|
|
Kode 6 digit telah dikirim ke email kamu. Berlaku selama
|
|
<strong id="timerDisplay">05:00</strong>.
|
|
</p>
|
|
|
|
{{-- Alert success --}}
|
|
@if (session('success'))
|
|
<div class="alert alert-success">{{ session('success') }}</div>
|
|
@endif
|
|
|
|
{{-- Alert error --}}
|
|
@if ($errors->any())
|
|
<div class="alert alert-error">{{ $errors->first() }}</div>
|
|
@endif
|
|
|
|
<div class="info-box">
|
|
<span>📧</span>
|
|
<span>Cek folder <strong>Spam</strong> jika kode tidak muncul dalam 1 menit.</span>
|
|
</div>
|
|
|
|
{{-- Form verifikasi OTP --}}
|
|
<form method="POST" action="{{ route('password.otp.verify') }}" id="otpForm">
|
|
@csrf
|
|
|
|
<div class="form-group">
|
|
<label>Kode Verifikasi</label>
|
|
<div class="otp-group" id="otpGroup">
|
|
@for ($i = 0; $i < 6; $i++)
|
|
<input
|
|
type="text"
|
|
maxlength="1"
|
|
class="otp-input"
|
|
inputmode="numeric"
|
|
pattern="[0-9]"
|
|
autocomplete="off"
|
|
/>
|
|
@endfor
|
|
</div>
|
|
{{-- Hidden input yang akan dikirim --}}
|
|
<input type="hidden" name="otp" id="otpHidden" />
|
|
@error('otp')
|
|
<span class="invalid-feedback" style="display:block; margin-top:8px;">{{ $message }}</span>
|
|
@enderror
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary" id="verifyBtn" disabled>
|
|
Verifikasi Kode
|
|
</button>
|
|
</form>
|
|
|
|
{{-- Kirim ulang OTP --}}
|
|
<div class="resend">
|
|
Tidak menerima kode?
|
|
<form method="POST" action="{{ route('password.otp.resend') }}" style="display:inline;">
|
|
@csrf
|
|
<button type="submit" id="resendBtn" disabled>
|
|
Kirim Ulang (<span id="resendTimer">60</span>s)
|
|
</button>
|
|
</form>
|
|
</div>
|
|
|
|
<a href="{{ route('password.email') }}" class="btn btn-ghost" style="display:block; text-align:center; margin-top:12px; text-decoration:none;">
|
|
← Ganti Email
|
|
</a>
|
|
|
|
@endsection
|
|
|
|
@push('scripts')
|
|
<script>
|
|
// ── OTP Timer (5 menit) ──
|
|
let timerInterval;
|
|
(function startOTPTimer() {
|
|
let seconds = 300;
|
|
const display = document.getElementById('timerDisplay');
|
|
const tick = () => {
|
|
const m = String(Math.floor(seconds / 60)).padStart(2, '0');
|
|
const s = String(seconds % 60).padStart(2, '0');
|
|
display.textContent = m + ':' + s;
|
|
if (seconds-- <= 0) {
|
|
clearInterval(timerInterval);
|
|
display.textContent = 'Kedaluwarsa';
|
|
display.style.color = '#e74c3c';
|
|
}
|
|
};
|
|
tick();
|
|
timerInterval = setInterval(tick, 1000);
|
|
})();
|
|
|
|
// ── Resend countdown (60 detik) ──
|
|
(function startResendTimer() {
|
|
const btn = document.getElementById('resendBtn');
|
|
const span = document.getElementById('resendTimer');
|
|
btn.disabled = true;
|
|
let t = 60;
|
|
const interval = setInterval(() => {
|
|
span.textContent = --t;
|
|
if (t <= 0) {
|
|
clearInterval(interval);
|
|
btn.disabled = false;
|
|
btn.innerHTML = 'Kirim Ulang';
|
|
}
|
|
}, 1000);
|
|
})();
|
|
|
|
// ── OTP Input UX ──
|
|
const inputs = document.querySelectorAll('.otp-input');
|
|
const hidden = document.getElementById('otpHidden');
|
|
const verBtn = document.getElementById('verifyBtn');
|
|
|
|
function syncHidden() {
|
|
hidden.value = [...inputs].map(i => i.value).join('');
|
|
verBtn.disabled = hidden.value.length !== 6;
|
|
}
|
|
|
|
inputs.forEach((inp, idx) => {
|
|
inp.addEventListener('input', () => {
|
|
inp.value = inp.value.replace(/\D/g, '').slice(-1);
|
|
if (inp.value && idx < inputs.length - 1) inputs[idx + 1].focus();
|
|
syncHidden();
|
|
});
|
|
|
|
inp.addEventListener('keydown', e => {
|
|
if (e.key === 'Backspace' && !inp.value && idx > 0) {
|
|
inputs[idx - 1].focus();
|
|
}
|
|
});
|
|
|
|
inp.addEventListener('paste', e => {
|
|
e.preventDefault();
|
|
const pasted = (e.clipboardData || window.clipboardData)
|
|
.getData('text').replace(/\D/g, '').slice(0, 6);
|
|
pasted.split('').forEach((ch, i) => { if (inputs[i]) inputs[i].value = ch; });
|
|
syncHidden();
|
|
inputs[Math.min(pasted.length, inputs.length - 1)].focus();
|
|
});
|
|
});
|
|
|
|
// Auto-focus input pertama
|
|
inputs[0].focus();
|
|
</script>
|
|
@endpush |