otp email
This commit is contained in:
parent
d21b26a5d2
commit
7c322017da
|
|
@ -3,14 +3,135 @@
|
||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Repositories\Member\Auth\MemberAuthRepository;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class LoginEmailController extends Controller
|
class LoginEmailController extends Controller
|
||||||
{
|
{
|
||||||
|
protected $memberAuthRepository;
|
||||||
|
|
||||||
|
public function __construct(MemberAuthRepository $memberAuthRepository)
|
||||||
|
{
|
||||||
|
$this->memberAuthRepository = $memberAuthRepository;
|
||||||
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return view('account.signin',[
|
return view('account.signin',[
|
||||||
'type' => 'email',
|
'type' => 'email',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function otp(Request $request)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'identity' => 'required|email',
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$email = $request->identity;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Find user by email
|
||||||
|
$user = $this->memberAuthRepository->check(['email' => $email]);
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => __('signin.user_not_found'),
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Use MemberAuthRepository to generate OTP
|
||||||
|
$otp = $this->memberAuthRepository->emailOtp(['email' => $email]);
|
||||||
|
|
||||||
|
// TODO: Integrate with WhatsApp API to send OTP
|
||||||
|
// For now, we'll just log it (remove in production)
|
||||||
|
Log::info("OTP for {$email}: {$otp->otp}");
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => __('otp.sent'),
|
||||||
|
'redirect' => route('login-email.otp.view', ['identity' => $email]),
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('OTP generation failed: '.$e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => __('otp.generate_failed'),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Email login failed: ' . $e->getMessage());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => __('signin.login_failed')
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function otpView($identity)
|
||||||
|
{
|
||||||
|
return view('account.otp', [
|
||||||
|
'identity' => $identity,
|
||||||
|
'type' => 'email'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verify(Request $request)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'identity' => 'required|email',
|
||||||
|
'otp' => 'required|string|size:6',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return back()
|
||||||
|
->withErrors($validator)
|
||||||
|
->withInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
$identity = $request->identity;
|
||||||
|
$otp = $request->otp;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use MemberAuthRepository to verify OTP for email
|
||||||
|
$result = $this->memberAuthRepository->emailOtpConfirm([
|
||||||
|
'email' => $identity,
|
||||||
|
'otp' => $otp,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$check = $this->memberAuthRepository->check(['email' => $identity]);
|
||||||
|
|
||||||
|
$loginData = [
|
||||||
|
'fcm_token' => null,
|
||||||
|
'device' => 'web',
|
||||||
|
];
|
||||||
|
$this->memberAuthRepository->getAuth([
|
||||||
|
'user_id' => $check->id,
|
||||||
|
'device' => 'web',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->route('home')->with('success', __('otp.login_success'));
|
||||||
|
|
||||||
|
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||||
|
return back()
|
||||||
|
->withErrors(['otp' => $e->getMessage()])
|
||||||
|
->withInput();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Email OTP verification failed: '.$e->getMessage());
|
||||||
|
|
||||||
|
return back()
|
||||||
|
->withErrors(['otp' => __('otp.verification_failed')])
|
||||||
|
->withInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class LoginWaController extends Controller
|
||||||
|
|
||||||
$identity = $request->identity;
|
$identity = $request->identity;
|
||||||
|
|
||||||
// check first if user exists
|
// Find or create user by phone number
|
||||||
$user = $this->memberAuthRepository->check(['phone' => $identity]);
|
$user = $this->memberAuthRepository->check(['phone' => $identity]);
|
||||||
if (! $user) {
|
if (! $user) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
|
@ -106,8 +106,14 @@ class LoginWaController extends Controller
|
||||||
|
|
||||||
$check = $this->memberAuthRepository->check(['phone' => $identity]);
|
$check = $this->memberAuthRepository->check(['phone' => $identity]);
|
||||||
|
|
||||||
|
$loginData = [
|
||||||
// Auth::guard('web')->attempt(['id' => $check->id]);
|
'fcm_token' => null,
|
||||||
|
'device' => 'web',
|
||||||
|
];
|
||||||
|
$this->memberAuthRepository->getAuth([
|
||||||
|
'user_id' => $check->id,
|
||||||
|
'device' => 'web',
|
||||||
|
]);
|
||||||
|
|
||||||
return redirect()->route('home')->with('success', __('otp.login_success'));
|
return redirect()->route('home')->with('success', __('otp.login_success'));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,8 @@ class MemberAuthRepository
|
||||||
$phone = preg_replace("/[^0-9]/", "",$phone);
|
$phone = preg_replace("/[^0-9]/", "",$phone);
|
||||||
$phone_with_zero = "0" . substr($phone,2,strlen($phone));
|
$phone_with_zero = "0" . substr($phone,2,strlen($phone));
|
||||||
|
|
||||||
$affliator = Affiliator::where('phone', $phone)->orWhere('phone', $phone_with_zero)->first();
|
|
||||||
$customer = Customer::where('phone', $phone)->orWhere('phone', $phone_with_zero)->first();
|
$customer = Customer::where('phone', $phone)->orWhere('phone', $phone_with_zero)->first();
|
||||||
$user = $affliator ? $affliator->user : @$customer->user;
|
$user = @$customer->user;
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
@ -69,9 +68,8 @@ class MemberAuthRepository
|
||||||
{
|
{
|
||||||
$email = trim($email);
|
$email = trim($email);
|
||||||
|
|
||||||
$affliator = Affiliator::where('email', $email)->first();
|
|
||||||
$customer = Customer::where('email', $email)->first();
|
$customer = Customer::where('email', $email)->first();
|
||||||
$user = $affliator ? $affliator->user : @$customer->user;
|
$user = @$customer->user;
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ return [
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'title' => 'Verify Your Phone',
|
'title' => 'Verify Your :type',
|
||||||
'description' => 'We sent a 6-digit code to :phone',
|
'description' => 'We sent a 6-digit code to :phone',
|
||||||
'enter_code' => 'Enter verification code',
|
'enter_code' => 'Enter verification code',
|
||||||
'invalid_code' => 'Please enter a valid 6-digit code',
|
'invalid_code' => 'Please enter a valid 6-digit code',
|
||||||
|
|
@ -32,5 +32,6 @@ return [
|
||||||
'resend_failed' => 'Failed to resend OTP. Please try again',
|
'resend_failed' => 'Failed to resend OTP. Please try again',
|
||||||
'generate_failed' => 'Failed to generate OTP. Please try again',
|
'generate_failed' => 'Failed to generate OTP. Please try again',
|
||||||
'verification_failed' => 'OTP verification failed. Please try again',
|
'verification_failed' => 'OTP verification failed. Please try again',
|
||||||
|
'register_required' => 'Please register an account first',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,13 @@ return [
|
||||||
'facebook' => 'Facebook',
|
'facebook' => 'Facebook',
|
||||||
'apple' => 'Apple',
|
'apple' => 'Apple',
|
||||||
'need_help' => 'Need help?',
|
'need_help' => 'Need help?',
|
||||||
'rights_reserved' => '© All rights reserved. Made by <span class="animate-underline"><a class="animate-target text-dark-emphasis text-decoration-none" href="/" rel="noreferrer" target="_blank">:company</a></span>',
|
'rights_reserved' => ' All rights reserved. Made by <span class="animate-underline"><a class="animate-target text-dark-emphasis text-decoration-none" href="/" rel="noreferrer" target="_blank">:company',
|
||||||
|
'company_name' => 'Coderthemes',
|
||||||
'sending' => 'Sending...',
|
'sending' => 'Sending...',
|
||||||
'error' => 'An error occurred. Please try again.',
|
'error' => 'An error occurred. Please try again.',
|
||||||
|
'invalid_credentials' => 'Invalid email or password',
|
||||||
|
'user_not_found' => 'User not found with this email',
|
||||||
|
'login_success' => 'Login successful',
|
||||||
|
'login_failed' => 'Login failed. Please try again.',
|
||||||
|
'password_field_required' => 'Password field is required for email login',
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -32,5 +32,6 @@ return [
|
||||||
'resend_failed' => 'Gagal mengirim ulang OTP. Silakan coba lagi',
|
'resend_failed' => 'Gagal mengirim ulang OTP. Silakan coba lagi',
|
||||||
'generate_failed' => 'Gagal membuat OTP. Silakan coba lagi',
|
'generate_failed' => 'Gagal membuat OTP. Silakan coba lagi',
|
||||||
'verification_failed' => 'Verifikasi OTP gagal. Silakan coba lagi',
|
'verification_failed' => 'Verifikasi OTP gagal. Silakan coba lagi',
|
||||||
|
'register_required' => 'Silakan daftar akun terlebih dahulu',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,13 @@ return [
|
||||||
'facebook' => 'Facebook',
|
'facebook' => 'Facebook',
|
||||||
'apple' => 'Apple',
|
'apple' => 'Apple',
|
||||||
'need_help' => 'Butuh bantuan?',
|
'need_help' => 'Butuh bantuan?',
|
||||||
'rights_reserved' => '© Semua hak dilindungi. Dibuat oleh <span class="animate-underline"><a class="animate-target text-dark-emphasis text-decoration-none" href="https://coderthemes.com/" rel="noreferrer" target="_blank">:company</a></span>',
|
'rights_reserved' => ' Semua hak dilindungi. Dibuat oleh <span class="animate-underline"><a class="animate-target text-dark-emphasis text-decoration-none" href="https://coderthemes.com/" rel="noreferrer" target="_blank">:company</a></span>',
|
||||||
|
'company_name' => 'Coderthemes',
|
||||||
'sending' => 'Mengirim...',
|
'sending' => 'Mengirim...',
|
||||||
'error' => 'Terjadi kesalahan. Silakan coba lagi.',
|
'error' => 'Terjadi kesalahan. Silakan coba lagi.',
|
||||||
|
'invalid_credentials' => 'Email atau kata sandi salah',
|
||||||
|
'user_not_found' => 'Pengguna tidak ditemukan dengan email ini',
|
||||||
|
'login_success' => 'Login berhasil',
|
||||||
|
'login_failed' => 'Login gagal. Silakan coba lagi.',
|
||||||
|
'password_field_required' => 'Kolom kata sandi diperlukan untuk login email',
|
||||||
];
|
];
|
||||||
|
|
@ -9,37 +9,36 @@
|
||||||
<header class="navbar px-0 pb-4 mt-n2 mt-sm-0 mb-2 mb-md-3 mb-lg-4">
|
<header class="navbar px-0 pb-4 mt-n2 mt-sm-0 mb-2 mb-md-3 mb-lg-4">
|
||||||
<a class="navbar-brand pt-0" href="/">
|
<a class="navbar-brand pt-0" href="/">
|
||||||
<span class="d-flex flex-shrink-0 text-primary me-2">
|
<span class="d-flex flex-shrink-0 text-primary me-2">
|
||||||
<img src="{{ asset('logo/logo-colored.png') }}" alt="AsiaGolf Logo" style="height: 36px; width: auto;">
|
<img src="{{ asset('logo/logo-colored.png') }}" alt="AsiaGolf Logo"
|
||||||
|
style="height: 36px; width: auto;">
|
||||||
</span>
|
</span>
|
||||||
AsiaGolf
|
|
||||||
</a>
|
</a>
|
||||||
</header>
|
</header>
|
||||||
<h1 class="h2 mt-auto">{{ __('otp.title') }}</h1>
|
<h1 class="h2 mt-auto">{{ __('otp.title', ['type' => $type == 'email' ? 'Email' : 'Phone']) }}</h1>
|
||||||
<p class="text-muted mb-4">{{ __('otp.description', ['phone' => $identity]) }}</p>
|
<div class="nav fs-sm mb-4">
|
||||||
|
{{ __('otp.description', ['phone' => $identity]) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- error message --}}
|
||||||
{{-- show message if error --}}
|
@if ($errors->any())
|
||||||
@if ($errors->has('otp'))
|
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
{{ $errors->first('otp') }}
|
@foreach ($errors->all() as $error)
|
||||||
|
<div>{{ $error }}</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<!-- Form -->
|
<!-- Form -->
|
||||||
<form class="needs-validation" novalidate method="POST" action="{{ route('login-phone.verify') }}">
|
<!-- Form -->
|
||||||
|
<form class="needs-validation" novalidate method="POST" action="{{ $type == 'email' ? route('login-email.verify') : route('login-phone.verify') }}">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="identity" value="{{ $identity }}">
|
<input type="hidden" name="identity" value="{{ $identity }}">
|
||||||
|
|
||||||
<div class="position-relative mb-4">
|
<div class="position-relative mb-4">
|
||||||
<label for="otp" class="form-label">{{ __('otp.enter_code') }}</label>
|
<label for="otp" class="form-label">{{ __('otp.enter_code') }}</label>
|
||||||
<input class="form-control form-control-lg" type="text"
|
<input class="form-control form-control-lg" type="text" placeholder="000000" maxlength="6"
|
||||||
placeholder="000000"
|
pattern="[0-9]{6}" required="" name="otp" id="otp" autocomplete="one-time-code" />
|
||||||
maxlength="6"
|
|
||||||
pattern="[0-9]{6}"
|
|
||||||
required=""
|
|
||||||
name="otp"
|
|
||||||
id="otp"
|
|
||||||
autocomplete="one-time-code" />
|
|
||||||
<div class="invalid-tooltip bg-transparent py-0">{{ __('otp.invalid_code') }}</div>
|
<div class="invalid-tooltip bg-transparent py-0">{{ __('otp.invalid_code') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -62,10 +61,12 @@
|
||||||
<footer class="mt-auto">
|
<footer class="mt-auto">
|
||||||
<div class="nav mb-4">
|
<div class="nav mb-4">
|
||||||
<a class="nav-link text-decoration-underline p-0"
|
<a class="nav-link text-decoration-underline p-0"
|
||||||
href="{{ route('second', ['help', 'topics-v1']) }}">{{ __('otp.need_help') }}</a>
|
href="{{ route('second', ['help', 'topics-v1']) }}">Need help?</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="fs-xs mb-0">
|
<p class="fs-xs mb-0">
|
||||||
{!! __('signin.rights_reserved', ['company' => config('app.name')]) !!}
|
© All rights reserved. Made by <span class="animate-underline"><a
|
||||||
|
class="animate-target text-dark-emphasis text-decoration-none"
|
||||||
|
href="https://coderthemes.com/" rel="noreferrer" target="_blank">Coderthemes</a></span>
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -86,61 +87,64 @@
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const otpInput = document.getElementById('otp');
|
const otpInput = document.getElementById('otp');
|
||||||
const resendBtn = document.getElementById('resend-otp');
|
const resendBtn = document.getElementById('resend-otp');
|
||||||
let resendTimer = null;
|
let resendTimer = null;
|
||||||
let countdown = 60;
|
let countdown = 60;
|
||||||
|
|
||||||
// Auto-focus OTP input
|
// Auto-focus OTP input
|
||||||
otpInput.focus();
|
otpInput.focus();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Resend OTP functionality
|
// Resend OTP functionality
|
||||||
resendBtn.addEventListener('click', function() {
|
resendBtn.addEventListener('click', function() {
|
||||||
if (resendTimer) return;
|
if (resendTimer) return;
|
||||||
|
|
||||||
const identity = document.querySelector('input[name="identity"]').value;
|
const identity = document.querySelector('input[name="identity"]').value;
|
||||||
|
|
||||||
fetch('{{ route("login-phone.otp") }}', {
|
fetch('{{ route('login-phone.otp') }}', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')
|
||||||
},
|
.getAttribute('content')
|
||||||
body: JSON.stringify({
|
},
|
||||||
identity: identity
|
body: JSON.stringify({
|
||||||
})
|
identity: identity
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
})
|
||||||
.then(data => {
|
.then(response => response.json())
|
||||||
if (data.success) {
|
.then(data => {
|
||||||
// Start countdown
|
if (data.success) {
|
||||||
resendBtn.disabled = true;
|
// Start countdown
|
||||||
countdown = 60;
|
resendBtn.disabled = true;
|
||||||
|
countdown = 60;
|
||||||
|
|
||||||
resendTimer = setInterval(function() {
|
resendTimer = setInterval(function() {
|
||||||
countdown--;
|
countdown--;
|
||||||
resendBtn.textContent = '{{ __("otp.resend_in") }} ' + countdown + 's';
|
resendBtn.textContent = '{{ __('otp.resend_in') }} ' +
|
||||||
|
countdown + 's';
|
||||||
|
|
||||||
if (countdown <= 0) {
|
if (countdown <= 0) {
|
||||||
clearInterval(resendTimer);
|
clearInterval(resendTimer);
|
||||||
resendTimer = null;
|
resendTimer = null;
|
||||||
resendBtn.disabled = false;
|
resendBtn.disabled = false;
|
||||||
resendBtn.innerHTML = '<span class="animate-target">{{ __("otp.resend_code") }}</span>';
|
resendBtn.innerHTML =
|
||||||
}
|
'<span class="animate-target">{{ __('otp.resend_code') }}</span>';
|
||||||
}, 1000);
|
}
|
||||||
} else {
|
}, 1000);
|
||||||
alert(data.message || '{{ __("otp.resend_failed") }}');
|
} else {
|
||||||
}
|
alert(data.message || '{{ __('otp.resend_failed') }}');
|
||||||
})
|
}
|
||||||
.catch(error => {
|
})
|
||||||
console.error('Error:', error);
|
.catch(error => {
|
||||||
alert('{{ __("otp.resend_failed") }}');
|
console.error('Error:', error);
|
||||||
|
alert('{{ __('otp.resend_failed') }}');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
</script>
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,16 @@
|
||||||
<header class="navbar px-0 pb-4 mt-n2 mt-sm-0 mb-2 mb-md-3 mb-lg-4">
|
<header class="navbar px-0 pb-4 mt-n2 mt-sm-0 mb-2 mb-md-3 mb-lg-4">
|
||||||
<a class="navbar-brand pt-0" href="/">
|
<a class="navbar-brand pt-0" href="/">
|
||||||
<span class="d-flex flex-shrink-0 text-primary me-2">
|
<span class="d-flex flex-shrink-0 text-primary me-2">
|
||||||
<img src="{{ asset('logo/logo-colored.png') }}" alt="AsiaGolf Logo" style="height: 36px; width: auto;">
|
<img src="{{ asset('logo/logo-colored.png') }}" alt="AsiaGolf Logo"
|
||||||
|
style="height: 36px; width: auto;">
|
||||||
</span>
|
</span>
|
||||||
AsiaGolf
|
|
||||||
</a>
|
</a>
|
||||||
</header>
|
</header>
|
||||||
<h1 class="h2 mt-auto">{{ __('signin.title') }}</h1>
|
<h1 class="h2 mt-auto">Welcome back</h1>
|
||||||
<div class="nav fs-sm mb-4">
|
<div class="nav fs-sm mb-4">
|
||||||
{{ __('signin.no_account') }}
|
Don't have an account?
|
||||||
<a class="nav-link text-decoration-underline p-0 ms-2"
|
<a class="nav-link text-decoration-underline p-0 ms-2"
|
||||||
href="{{ route('register') }}">{{ __('signin.create_account') }}</a>
|
href="{{ route('register') }}">Create an account</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- show error message --}}
|
{{-- show error message --}}
|
||||||
|
|
@ -27,8 +27,12 @@
|
||||||
<!-- Form -->
|
<!-- Form -->
|
||||||
<form class="needs-validation" id="loginForm" novalidate="">
|
<form class="needs-validation" id="loginForm" novalidate="">
|
||||||
<div class="position-relative mb-4">
|
<div class="position-relative mb-4">
|
||||||
<input class="form-control form-control-lg" placeholder="{{ $type == 'email' ? __('signin.email_placeholder') : __('signin.phone_placeholder') }}" required="" name="identity" id="identity" min="1" type="{{ $type == 'email' ? 'email' :'number' }}" />
|
<input class="form-control form-control-lg"
|
||||||
<div class="invalid-tooltip bg-transparent py-0">{{ $type == 'email' ? __('signin.email_invalid') : __('signin.phone_invalid') }}</div>
|
placeholder="{{ $type == 'email' ? __('signin.email_placeholder') : __('signin.phone_placeholder') }}"
|
||||||
|
required="" name="identity" id="identity" min="1"
|
||||||
|
type="{{ $type == 'email' ? 'email' : 'number' }}" />
|
||||||
|
<div class="invalid-tooltip bg-transparent py-0">
|
||||||
|
{{ $type == 'email' ? __('signin.email_invalid') : __('signin.phone_invalid') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center justify-content-end mb-4">
|
<div class="d-flex align-items-center justify-content-end mb-4">
|
||||||
|
|
@ -39,7 +43,8 @@
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<a class="nav-link animate-underline p-0"
|
<a class="nav-link animate-underline p-0"
|
||||||
href="{{ $type == 'email' ? route('login') : route('login-email') }}">
|
href="{{ $type == 'email' ? route('login') : route('login-email') }}">
|
||||||
<span class="animate-target">{{ $type == 'email' ? __('signin.login_with_phone') : __('signin.login_with_email') }}</span>
|
<span
|
||||||
|
class="animate-target">{{ $type == 'email' ? __('signin.login_with_phone') : __('signin.login_with_email') }}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -48,13 +53,15 @@
|
||||||
<x-social-login />
|
<x-social-login />
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<footer class="mt-auto">
|
<footer class="mt-auto">
|
||||||
<div class="nav mb-4">
|
{{-- <div class="nav mb-4">
|
||||||
<a class="nav-link text-decoration-underline p-0"
|
<a class="nav-link text-decoration-underline p-0"
|
||||||
href="{{ route('second', ['help', 'topics-v1']) }}">{{ __('signin.need_help') }}</a>
|
href="{{ route('second', ['help', 'topics-v1']) }}">Need help?</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="fs-xs mb-0">
|
<p class="fs-xs mb-0">
|
||||||
{!! __('signin.rights_reserved', ['company' => config('app.name')]) !!}
|
© All rights reserved. Made by <span class="animate-underline"><a
|
||||||
</p>
|
class="animate-target text-dark-emphasis text-decoration-none"
|
||||||
|
href="https://coderthemes.com/" rel="noreferrer" target="_blank">Coderthemes</a></span>
|
||||||
|
</p> --}}
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<!-- Cover image visible on screens > 992px wide (lg breakpoint) -->
|
<!-- Cover image visible on screens > 992px wide (lg breakpoint) -->
|
||||||
|
|
@ -74,81 +81,130 @@
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const loginForm = document.getElementById('loginForm');
|
const loginForm = document.getElementById('loginForm');
|
||||||
const submitBtn = loginForm.querySelector('button[type="submit"]');
|
const submitBtn = loginForm.querySelector('button[type="submit"]');
|
||||||
const identityInput = document.getElementById('identity');
|
const identityInput = document.getElementById('identity');
|
||||||
|
const passwordInput = document.getElementById('password');
|
||||||
|
|
||||||
// Hide error message when user starts typing
|
// Hide error message when user starts typing
|
||||||
identityInput.addEventListener('input', function() {
|
identityInput.addEventListener('input', function() {
|
||||||
const errorDiv = document.getElementById('error-message');
|
|
||||||
errorDiv.classList.add('d-none');
|
|
||||||
});
|
|
||||||
|
|
||||||
loginForm.addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
// Hide error message when submitting
|
|
||||||
const errorDiv = document.getElementById('error-message');
|
|
||||||
errorDiv.classList.add('d-none');
|
|
||||||
|
|
||||||
// Reset validation
|
|
||||||
loginForm.classList.remove('was-validated');
|
|
||||||
|
|
||||||
// Basic validation
|
|
||||||
if (!identityInput.value.trim()) {
|
|
||||||
loginForm.classList.add('was-validated');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show loading state
|
|
||||||
submitBtn.disabled = true;
|
|
||||||
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>{{ __("signin.sending") }}';
|
|
||||||
|
|
||||||
// Determine form type based on current route
|
|
||||||
const isPhoneLogin = '{{ $type }}' === 'phone';
|
|
||||||
|
|
||||||
if (isPhoneLogin) {
|
|
||||||
// Send OTP request for phone login
|
|
||||||
fetch('{{ route("login-phone.otp") }}', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
identity: identityInput.value.trim()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
// Redirect to OTP verification page
|
|
||||||
window.location.href = data.redirect;
|
|
||||||
} else {
|
|
||||||
// Show error in error message div
|
|
||||||
const errorDiv = document.getElementById('error-message');
|
|
||||||
errorDiv.textContent = data.message || '{{ __("signin.error") }}';
|
|
||||||
errorDiv.classList.remove('d-none');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error:', error);
|
|
||||||
const errorDiv = document.getElementById('error-message');
|
const errorDiv = document.getElementById('error-message');
|
||||||
errorDiv.textContent = '{{ __("signin.error") }}';
|
errorDiv.classList.add('d-none');
|
||||||
errorDiv.classList.remove('d-none');
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
// Reset button state
|
|
||||||
submitBtn.disabled = false;
|
|
||||||
submitBtn.textContent = '{{ __("signin.sign_in") }}';
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
// Handle email login (traditional form submission)
|
// Hide error message when password field is typed
|
||||||
loginForm.submit();
|
if (passwordInput) {
|
||||||
}
|
passwordInput.addEventListener('input', function() {
|
||||||
});
|
const errorDiv = document.getElementById('error-message');
|
||||||
});
|
errorDiv.classList.add('d-none');
|
||||||
</script>
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loginForm.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Hide error message when submitting
|
||||||
|
const errorDiv = document.getElementById('error-message');
|
||||||
|
errorDiv.classList.add('d-none');
|
||||||
|
|
||||||
|
// Reset validation
|
||||||
|
loginForm.classList.remove('was-validated');
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
if (!identityInput.value.trim()) {
|
||||||
|
loginForm.classList.add('was-validated');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passwordInput && !passwordInput.value.trim()) {
|
||||||
|
loginForm.classList.add('was-validated');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
submitBtn.innerHTML =
|
||||||
|
'<span class="spinner-border spinner-border-sm me-2"></span>{{ __('signin.sending') }}';
|
||||||
|
|
||||||
|
// Determine form type based on current route
|
||||||
|
const isPhoneLogin = '{{ $type }}' === 'phone';
|
||||||
|
|
||||||
|
if (isPhoneLogin) {
|
||||||
|
// Send OTP request for phone login
|
||||||
|
fetch('{{ route('login-phone.otp') }}', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')
|
||||||
|
.getAttribute('content')
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
identity: identityInput.value.trim()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Redirect to OTP verification page
|
||||||
|
window.location.href = data.redirect;
|
||||||
|
} else {
|
||||||
|
// Show error in error message div
|
||||||
|
const errorDiv = document.getElementById('error-message');
|
||||||
|
errorDiv.textContent = data.message || '{{ __('signin.error') }}';
|
||||||
|
errorDiv.classList.remove('d-none');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
const errorDiv = document.getElementById('error-message');
|
||||||
|
errorDiv.textContent = '{{ __('signin.error') }}';
|
||||||
|
errorDiv.classList.remove('d-none');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// Reset button state
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.textContent = '{{ __('signin.sign_in') }}';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
fetch('{{ route('login-email.otp') }}', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')
|
||||||
|
.getAttribute('content')
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
identity: identityInput.value.trim(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Redirect to home page
|
||||||
|
window.location.href = data.redirect;
|
||||||
|
} else {
|
||||||
|
// Show error in error message div
|
||||||
|
const errorDiv = document.getElementById('error-message');
|
||||||
|
errorDiv.textContent = data.message || '{{ __('signin.error') }}';
|
||||||
|
errorDiv.classList.remove('d-none');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
const errorDiv = document.getElementById('error-message');
|
||||||
|
errorDiv.textContent = '{{ __('signin.error') }}';
|
||||||
|
errorDiv.classList.remove('d-none');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// Reset button state
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.textContent = '{{ __('signin.sign_in') }}';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
||||||
|
|
@ -120,14 +120,16 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<footer class="mt-auto">
|
<footer class="mt-auto">
|
||||||
<div class="nav mb-4">
|
{{-- <div class="nav mb-4">
|
||||||
<a class="nav-link text-decoration-underline p-0"
|
<a class="nav-link text-decoration-underline p-0"
|
||||||
href="{{ route('second', ['help', 'topics-v1']) }}">{{ __('signup.need_help') }}</a>
|
href="{{ route('second', ['help', 'topics-v1']) }}">Need help?</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="fs-xs mb-0">
|
<p class="fs-xs mb-0">
|
||||||
{!! __('signin.rights_reserved', ['company' => config('app.name')]) !!}
|
© All rights reserved. Made by <span class="animate-underline"><a
|
||||||
</p>
|
class="animate-target text-dark-emphasis text-decoration-none"
|
||||||
|
href="https://coderthemes.com/" rel="noreferrer" target="_blank">Coderthemes</a></span>
|
||||||
|
</p> --}}
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<!-- Benefits section that turns into offcanvas on screens < 992px wide (lg breakpoint) -->
|
<!-- Benefits section that turns into offcanvas on screens < 992px wide (lg breakpoint) -->
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,17 @@ Route::get('/register', [RegisterController::class, 'index'])->name('register');
|
||||||
Route::post('/register', [RegisterController::class, 'register'])->name('register');
|
Route::post('/register', [RegisterController::class, 'register'])->name('register');
|
||||||
|
|
||||||
Route::get('/login', [LoginWaController::class, 'index'])->name('login');
|
Route::get('/login', [LoginWaController::class, 'index'])->name('login');
|
||||||
Route::get('/login/phone', [LoginWaController::class, 'index'])->name('login-phone');
|
Route::group(['prefix' => '/login/phone'], function () {
|
||||||
Route::post('/login/phone/otp', [LoginWaController::class,'otp'])->name('login-phone.otp');
|
Route::get('/', [LoginWaController::class, 'index'])->name('login-phone');
|
||||||
Route::get('/login/phone/otp/{identity}', [LoginWaController::class, 'otpView'])->name('login-phone.otp.view');
|
Route::post('/otp', [LoginWaController::class,'otp'])->name('login-phone.otp');
|
||||||
Route::post('/login/phone/verify', [LoginWaController::class, 'verify'])->name('login-phone.verify');
|
Route::get('/otp/{identity}', [LoginWaController::class, 'otpView'])->name('login-phone.otp.view');
|
||||||
|
Route::post('/verify', [LoginWaController::class, 'verify'])->name('login-phone.verify');
|
||||||
|
});
|
||||||
|
|
||||||
Route::get('/login/email', [LoginEmailController::class, 'index'])->name('login-email');
|
Route::group(['prefix' => '/login/email'], function () {
|
||||||
|
Route::get('/', [LoginEmailController::class, 'index'])->name('login-email');
|
||||||
|
Route::post('/otp', [LoginEmailController::class, 'otp'])->name('login-email.otp');
|
||||||
|
Route::get('/otp/{identity}', [LoginEmailController::class, 'otpView'])->name('login-email.otp.view');
|
||||||
|
Route::post('/verify', [LoginEmailController::class, 'verify'])->name('login-email.verify');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue