good so far
This commit is contained in:
@@ -20,7 +20,7 @@ class VerificationController extends Controller
|
|||||||
$request->validate([
|
$request->validate([
|
||||||
'phone' => ['required', 'regex:/^\+993 [67]\d \d{2} \d{2} \d{2}$/']
|
'phone' => ['required', 'regex:/^\+993 [67]\d \d{2} \d{2} \d{2}$/']
|
||||||
], [
|
], [
|
||||||
'phone.regex' => 'The phone number must be in the format: +993 KX XX XX XX (where K is 6 or 7).'
|
'phone.regex' => 'Telefon belgiňiz şu formatda bolmaly: +993 KX XX XX XX (bu ýerde K 6 ýa-da 7).'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$phone = unmask_phone($request->phone);
|
$phone = unmask_phone($request->phone);
|
||||||
@@ -28,9 +28,6 @@ class VerificationController extends Controller
|
|||||||
// Generate a 4-digit OTP
|
// Generate a 4-digit OTP
|
||||||
$otp = (string) rand(1000, 9999);
|
$otp = (string) rand(1000, 9999);
|
||||||
|
|
||||||
// Log it to simulate SMS
|
|
||||||
Log::info('Tassyklaýyş belgi: ' . $otp);
|
|
||||||
|
|
||||||
// Store in DB
|
// Store in DB
|
||||||
PhoneVerification::updateOrCreate(
|
PhoneVerification::updateOrCreate(
|
||||||
['phone' => $phone],
|
['phone' => $phone],
|
||||||
@@ -60,6 +57,32 @@ class VerificationController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function resendOtp()
|
||||||
|
{
|
||||||
|
$phone = session()->get('verify_phone');
|
||||||
|
|
||||||
|
if (!$phone) {
|
||||||
|
return redirect()->route('verification.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a 4-digit OTP
|
||||||
|
$otp = (string) rand(1000, 9999);
|
||||||
|
|
||||||
|
// Store in DB
|
||||||
|
PhoneVerification::updateOrCreate(
|
||||||
|
['phone' => $phone],
|
||||||
|
[
|
||||||
|
'otp' => $otp,
|
||||||
|
'expires_at' => now()->addMinutes(10),
|
||||||
|
'verified' => false
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
sendSMS($phone, $otp);
|
||||||
|
|
||||||
|
return back()->with('status', 'Täze kod iberildi!');
|
||||||
|
}
|
||||||
|
|
||||||
public function verifyOtp(Request $request)
|
public function verifyOtp(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
@@ -70,11 +93,11 @@ class VerificationController extends Controller
|
|||||||
$verification = PhoneVerification::query()->where('phone', $request->phone)->first();
|
$verification = PhoneVerification::query()->where('phone', $request->phone)->first();
|
||||||
|
|
||||||
if (!$verification || $verification->otp !== $request->otp) {
|
if (!$verification || $verification->otp !== $request->otp) {
|
||||||
return back()->withErrors(['otp' => 'Invalid OTP.']);
|
return back()->withErrors(['otp' => 'Nädogry kod.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($verification->expires_at < now()) {
|
if ($verification->expires_at < now()) {
|
||||||
return back()->withErrors(['otp' => 'OTP has expired.']);
|
return back()->withErrors(['otp' => 'Kodyň möhleti gutardy.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark as verified
|
// Mark as verified
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
app()->setLocale('tk');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'timezone' => 'UTC',
|
'timezone' => 'Asia/Ashgabat',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="light" lang="en">
|
<html class="light" lang="tk">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title>OTP Verification</title>
|
<title>OTP Tassyklamak</title>
|
||||||
<!-- Fonts -->
|
<!-- Fonts -->
|
||||||
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
||||||
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
<!-- TopAppBar Section -->
|
<!-- TopAppBar Section -->
|
||||||
<header class="flex items-center justify-between px-container-padding h-16 w-full z-50 fixed top-0 bg-background">
|
<header class="flex items-center justify-between px-container-padding h-16 w-full z-50 fixed top-0 bg-background">
|
||||||
@yield('header-back')
|
@yield('header-back')
|
||||||
<h1 class="font-headline-lg-mobile text-headline-lg-mobile font-bold text-primary">Verification</h1>
|
<h1 class="font-headline-lg-mobile text-headline-lg-mobile font-bold text-primary">Tassyklamak</h1>
|
||||||
<div class="w-10"></div> <!-- Spacer for center alignment -->
|
<div class="w-10"></div> <!-- Spacer for center alignment -->
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -48,20 +48,20 @@
|
|||||||
|
|
||||||
<!-- Typography Block -->
|
<!-- Typography Block -->
|
||||||
<div class="text-center mb-2xl">
|
<div class="text-center mb-2xl">
|
||||||
<h2 class="font-headline-xl text-headline-xl text-on-surface mb-sm">Congratulations!</h2>
|
<h2 class="font-headline-xl text-headline-xl text-on-surface mb-sm">Gutlaýarys!</h2>
|
||||||
<p class="font-body-md text-body-md text-on-surface-variant max-w-[280px] mx-auto">Your coupon code is ready to use.</p>
|
<p class="font-body-md text-body-md text-on-surface-variant max-w-[280px] mx-auto">Kupon koduňyz ulanmaga taýýar.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Styled Coupon Card -->
|
<!-- Styled Coupon Card -->
|
||||||
<div class="w-full max-w-sm relative float-animation">
|
<div class="w-full max-w-sm relative float-animation">
|
||||||
<div class="bg-surface-container-highest coupon-cutout rounded-lg p-lg flex flex-col items-center border border-outline-variant/30">
|
<div class="bg-surface-container-highest coupon-cutout rounded-lg p-lg flex flex-col items-center border border-outline-variant/30">
|
||||||
<div class="text-on-surface-variant font-label-md text-label-md mb-md uppercase tracking-widest">Limited Offer Code</div>
|
<div class="text-on-surface-variant font-label-md text-label-md mb-md uppercase tracking-widest">Çäklendirilen Teklip Kody</div>
|
||||||
<div class="bg-surface-container-lowest border-2 border-dashed border-primary/30 rounded-md px-xl py-lg w-full text-center mb-md group transition-all hover:border-primary">
|
<div class="bg-surface-container-lowest border-2 border-dashed border-primary/30 rounded-md px-xl py-lg w-full text-center mb-md group transition-all hover:border-primary">
|
||||||
<span class="font-mono text-4xl font-bold text-primary tracking-widest" id="couponCode">{{ $code }}</span>
|
<span class="font-mono text-4xl font-bold text-primary tracking-widest" id="couponCode">{{ $code }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="flex items-center gap-2 text-primary font-label-md text-label-md hover:bg-primary-container/10 px-md py-sm rounded-full transition-colors active:scale-95" onclick="copyCode()">
|
<button class="flex items-center gap-2 text-primary font-label-md text-label-md hover:bg-primary-container/10 px-md py-sm rounded-full transition-colors active:scale-95" onclick="copyCode()">
|
||||||
<span class="material-symbols-outlined" id="copyIcon">content_copy</span>
|
<span class="material-symbols-outlined" id="copyIcon">content_copy</span>
|
||||||
<span id="copyText">Copy Code</span>
|
<span id="copyText">Kody göçür</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Shadow Depth -->
|
<!-- Shadow Depth -->
|
||||||
@@ -71,10 +71,10 @@
|
|||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
<div class="w-full max-w-sm mt-2xl flex flex-col gap-md">
|
<div class="w-full max-w-sm mt-2xl flex flex-col gap-md">
|
||||||
<button class="w-full bg-primary text-on-primary py-md rounded-full font-label-md text-label-md shadow-[0_4px_12px_rgba(0,105,72,0.3)] hover:opacity-90 active:scale-95 transition-all">
|
<button class="w-full bg-primary text-on-primary py-md rounded-full font-label-md text-label-md shadow-[0_4px_12px_rgba(0,105,72,0.3)] hover:opacity-90 active:scale-95 transition-all">
|
||||||
Redeem Now
|
Häzir ulan
|
||||||
</button>
|
</button>
|
||||||
<a href="{{ route('verification.index') }}" class="w-full text-center bg-transparent text-primary py-md rounded-full font-label-md text-label-md hover:bg-primary/5 active:scale-95 transition-all">
|
<a href="{{ route('verification.index') }}" class="w-full text-center bg-transparent text-primary py-md rounded-full font-label-md text-label-md hover:bg-primary/5 active:scale-95 transition-all">
|
||||||
Back to Home
|
Baş sahypa dolan
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
<div class="mt-2xl w-full max-w-sm bg-secondary-container/30 border border-secondary-container rounded-lg p-md flex gap-md items-start">
|
<div class="mt-2xl w-full max-w-sm bg-secondary-container/30 border border-secondary-container rounded-lg p-md flex gap-md items-start">
|
||||||
<span class="material-symbols-outlined text-secondary">info</span>
|
<span class="material-symbols-outlined text-secondary">info</span>
|
||||||
<p class="font-body-sm text-body-sm text-on-secondary-container">
|
<p class="font-body-sm text-body-sm text-on-secondary-container">
|
||||||
This coupon is valid for the next 24 hours only. Make sure to apply it during checkout to enjoy your discount.
|
Bu kupon diňe öňümizdäki 24 sagadyň dowamynda hereket edýär. Arzanladyşdan peýdalanmak üçin töleg wagtynda ulanmagy ýatdan çykarmaň.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -153,11 +153,11 @@
|
|||||||
const btnText = document.getElementById('copyText');
|
const btnText = document.getElementById('copyText');
|
||||||
const btnIcon = document.getElementById('copyIcon');
|
const btnIcon = document.getElementById('copyIcon');
|
||||||
|
|
||||||
btnText.innerText = 'Copied!';
|
btnText.innerText = 'Göçürildi!';
|
||||||
btnIcon.innerText = 'check';
|
btnIcon.innerText = 'check';
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
btnText.innerText = 'Copy Code';
|
btnText.innerText = 'Kody göçür';
|
||||||
btnIcon.innerText = 'content_copy';
|
btnIcon.innerText = 'content_copy';
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@
|
|||||||
|
|
||||||
<!-- Headline & Subtext -->
|
<!-- Headline & Subtext -->
|
||||||
<section class="space-y-base mb-2xl">
|
<section class="space-y-base mb-2xl">
|
||||||
<h2 class="font-headline-xl text-headline-xl text-on-background">Enter your phone number</h2>
|
<h2 class="font-headline-xl text-headline-xl text-on-background">Telefon belgiňizi giriziň</h2>
|
||||||
<p class="font-body-md text-body-md text-on-surface-variant">We'll send a 4-digit code to verify your account.</p>
|
<p class="font-body-md text-body-md text-on-surface-variant">Hasabyňyzy tassyklamak üçin 4 sanly kod ibereris.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Phone Input Block -->
|
<!-- Phone Input Block -->
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
<div class="flex items-start gap-sm bg-secondary-container/30 p-md rounded-DEFAULT border border-secondary-container">
|
<div class="flex items-start gap-sm bg-secondary-container/30 p-md rounded-DEFAULT border border-secondary-container">
|
||||||
<span class="material-symbols-outlined text-on-secondary-container mt-0.5" data-icon="info">info</span>
|
<span class="material-symbols-outlined text-on-secondary-container mt-0.5" data-icon="info">info</span>
|
||||||
<p class="font-body-sm text-body-sm text-on-secondary-container">
|
<p class="font-body-sm text-body-sm text-on-secondary-container">
|
||||||
Message and data rates may apply. You will receive one SMS code for verification purposes.
|
Tassyklaýyş maksady bilen size bir SMS kod iberiler.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,10 +60,7 @@
|
|||||||
<!-- Footer Action -->
|
<!-- Footer Action -->
|
||||||
<div class="mt-2xl">
|
<div class="mt-2xl">
|
||||||
<button type="submit" class="w-full bg-primary text-on-primary h-14 rounded-lg font-label-md text-label-md uppercase tracking-widest soft-glow-btn hover:opacity-90 transition-all active:scale-[0.98]">
|
<button type="submit" class="w-full bg-primary text-on-primary h-14 rounded-lg font-label-md text-label-md uppercase tracking-widest soft-glow-btn hover:opacity-90 transition-all active:scale-[0.98]">
|
||||||
Send Code
|
Kody iber
|
||||||
</button>
|
|
||||||
<button type="button" class="w-full mt-md text-primary font-label-md text-label-md h-12 hover:bg-primary/5 transition-colors rounded-DEFAULT">
|
|
||||||
Try another method
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -31,9 +31,9 @@
|
|||||||
<div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-secondary-container mb-lg">
|
<div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-secondary-container mb-lg">
|
||||||
<span class="material-symbols-outlined text-primary text-4xl" data-icon="app_registration">app_registration</span>
|
<span class="material-symbols-outlined text-primary text-4xl" data-icon="app_registration">app_registration</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="font-headline-xl text-headline-xl text-on-surface mb-sm">Verify your number</h2>
|
<h2 class="font-headline-xl text-headline-xl text-on-surface mb-sm">Belgiňizi tassyklaň</h2>
|
||||||
<p class="font-body-md text-body-md text-on-surface-variant max-w-[280px] mx-auto">
|
<p class="font-body-md text-body-md text-on-surface-variant max-w-[280px] mx-auto">
|
||||||
Enter the 4-digit code sent to <span class="font-semibold text-on-surface">{{ $phone }}</span>
|
Şu belgä iberilen 4 sanly kody giriziň: <span class="font-semibold text-on-surface">+933 {{ $phone }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -43,6 +43,12 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if (session('status'))
|
||||||
|
<div class="text-primary text-center mt-md">
|
||||||
|
{{ session('status') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<form action="{{ route('verification.verify') }}" method="POST" id="otp-form">
|
<form action="{{ route('verification.verify') }}" method="POST" id="otp-form">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="phone" value="{{ $phone }}">
|
<input type="hidden" name="phone" value="{{ $phone }}">
|
||||||
@@ -59,22 +65,25 @@
|
|||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div class="mt-2xl space-y-md">
|
<div class="mt-2xl space-y-md">
|
||||||
<button type="button" id="verify-btn" class="glow-button w-full h-14 bg-primary text-on-primary font-label-md text-label-md rounded-full hover:opacity-90 active:scale-[0.98] transition-all">
|
<button type="button" id="verify-btn" class="glow-button w-full h-14 bg-primary text-on-primary font-label-md text-label-md rounded-full hover:opacity-90 active:scale-[0.98] transition-all">
|
||||||
Verify
|
Tassykla
|
||||||
</button>
|
</button>
|
||||||
<div class="text-center">
|
|
||||||
<button type="button" class="font-label-md text-label-md text-primary hover:underline transition-all">
|
|
||||||
Resend Code
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="text-center mt-md">
|
||||||
|
<form action="{{ route('verification.resend') }}" method="POST" class="inline">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="font-label-md text-label-md text-primary hover:underline transition-all">
|
||||||
|
Kody täzeden iber
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Info Card -->
|
<!-- Info Card -->
|
||||||
<div class="mt-auto mb-xl p-md rounded-DEFAULT border border-outline-variant bg-surface-container-low">
|
<div class="mt-auto mb-xl p-md rounded-DEFAULT border border-outline-variant bg-surface-container-low">
|
||||||
<div class="flex gap-sm">
|
<div class="flex gap-sm">
|
||||||
<span class="material-symbols-outlined text-on-surface-variant" data-icon="info">info</span>
|
<span class="material-symbols-outlined text-on-surface-variant" data-icon="info">info</span>
|
||||||
<p class="font-body-sm text-body-sm text-on-surface-variant">
|
<p class="font-body-sm text-body-sm text-on-surface-variant">
|
||||||
We use this to keep your account secure. Your number will not be shared with others.
|
Muny hasabyňyzyň howpsuzlygyny üpjün etmek üçin ulanýarys. Belgiňiz başgalar bilen paýlaşylmaz.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,13 +117,13 @@
|
|||||||
if (code.length === 4) {
|
if (code.length === 4) {
|
||||||
hiddenOtp.value = code;
|
hiddenOtp.value = code;
|
||||||
|
|
||||||
verifyBtn.innerHTML = '<span class="flex items-center justify-center gap-sm"><svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Verifying...</span>';
|
verifyBtn.innerHTML = '<span class="flex items-center justify-center gap-sm"><svg class="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Tassyklanýar...</span>';
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
form.submit();
|
form.submit();
|
||||||
}, 500); // small delay to show animation
|
}, 500); // small delay to show animation
|
||||||
} else {
|
} else {
|
||||||
alert('Please enter a 4-digit code.');
|
alert('Haýyş, 4 sanly kody giriziň.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ Route::get('/', [VerificationController::class, 'index'])->name('verification.in
|
|||||||
Route::post('/send-otp', [VerificationController::class, 'sendOtp'])->name('verification.send');
|
Route::post('/send-otp', [VerificationController::class, 'sendOtp'])->name('verification.send');
|
||||||
Route::get('/verify', [VerificationController::class, 'verifyForm'])->name('verification.verify.form');
|
Route::get('/verify', [VerificationController::class, 'verifyForm'])->name('verification.verify.form');
|
||||||
Route::post('/verify', [VerificationController::class, 'verifyOtp'])->name('verification.verify');
|
Route::post('/verify', [VerificationController::class, 'verifyOtp'])->name('verification.verify');
|
||||||
|
Route::post('/resend-otp', [VerificationController::class, 'resendOtp'])->name('verification.resend');
|
||||||
Route::get('/congratulations', [VerificationController::class, 'congratulations'])->name('verification.congratulations');
|
Route::get('/congratulations', [VerificationController::class, 'congratulations'])->name('verification.congratulations');
|
||||||
|
|||||||
Reference in New Issue
Block a user