sms and update admin

changes
This commit is contained in:
2026-06-12 12:57:23 +05:45
parent 3b529327bf
commit 85e87beaf6
8 changed files with 519 additions and 13 deletions
+35
View File
@@ -0,0 +1,35 @@
<?php
if (! function_exists('sendSMS')) {
function sendSMS(string $destination, string $message): bool
{
$key = env('SMS_API') ?: '';
$sender = env('SMS_SENDER') ?: 'SMSBit';
$url = env('SMS_URL') ?: 'https://sms.bibhutisolutions.com/smsapi/index';
$params = http_build_query([
'key' => $key,
'contacts' => $destination,
'senderid' => $sender,
'msg' => $message,
'responsetype' => 'json',
]);
$separator = parse_url($url, PHP_URL_QUERY) ? '&' : '?';
$url_final = $url . $separator . $params;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url_final);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($response === false || $httpCode >= 400) {
return false;
}
return true;
}
}
+1
View File
@@ -71,6 +71,7 @@ class HomeController extends Controller
$data['registrations'] = $query; $data['registrations'] = $query;
$data['search'] = $search; $data['search'] = $search;
$data['total'] = Registration::count(); $data['total'] = Registration::count();
$data['todayTotal'] = $query->total();
$data['played'] = $registrations->filter(fn($r) => !empty($r['today_goals']) && count($r['today_goals']) >= 3)->count(); $data['played'] = $registrations->filter(fn($r) => !empty($r['today_goals']) && count($r['today_goals']) >= 3)->count();
$data['topScore'] = $registrations->max('total_score') ?? 0; $data['topScore'] = $registrations->max('total_score') ?? 0;
$data['countries'] = Country::where('status', 1)->orderBy('title')->get(['id','title','country_flag']); $data['countries'] = Country::where('status', 1)->orderBy('title')->get(['id','title','country_flag']);
@@ -8,6 +8,8 @@ use App\Models\GameShot;
use App\Models\Registration; use App\Models\Registration;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Mail;
class RegistrationController extends Controller class RegistrationController extends Controller
{ {
@@ -64,6 +66,15 @@ class RegistrationController extends Controller
'score' => 0, 'score' => 0,
]); ]);
if ($request->filled('email')) {
dispatch(function () use ($registration) {
Mail::send('emails.welcome', ['registration' => $registration], function ($message) use ($registration) {
$message->to($registration->email)
->subject('Welcome to the Score & Win Challenge 2026! ⚽');
});
})->afterResponse();
}
return response()->json([ return response()->json([
'status' => 'session_created', 'status' => 'session_created',
'registration' => [ 'registration' => [
@@ -95,9 +106,11 @@ class RegistrationController extends Controller
Cache::put("otp_$phone", $otp, now()->addMinutes(2)); Cache::put("otp_$phone", $otp, now()->addMinutes(2));
sendSMS($phone, "Your OTP for Score & Win Challenge 2026 is: $otp. It is valid for 2 minutes.");
return response()->json([ return response()->json([
'status' => true, 'status' => true,
'otp' => $otp // remove in production // 'otp' => $otp // remove in production
]); ]);
} }
@@ -263,6 +276,16 @@ class RegistrationController extends Controller
$registration = $session->registration; $registration = $session->registration;
$registration->update(['total_score' => $registration->sessions()->sum('score')]); $registration->update(['total_score' => $registration->sessions()->sum('score')]);
if ($registration->email && $request->shot_number == 3) {
dispatch(function () use ($registration) {
Mail::send('emails.score', ['registration' => $registration], function ($message) use ($registration) {
$message->to($registration->email)
->subject('Your Score Update for the Score & Win Challenge 2026! ⚽');
});
})->afterResponse();
}
return response()->json([ return response()->json([
'status' => 'ok', 'status' => 'ok',
'total_score' => $registration->fresh()->total_score, 'total_score' => $registration->fresh()->total_score,
+4 -1
View File
@@ -26,7 +26,10 @@
"App\\": "app/", "App\\": "app/",
"Database\\Factories\\": "database/factories/", "Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/" "Database\\Seeders\\": "database/seeders/"
} },
"files": [
"app/Helpers/Helper.php"
]
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
+10 -10
View File
@@ -97,7 +97,7 @@
</svg> </svg>
Today's Sessions Today's Sessions
</div> </div>
<span class="text-xs font-medium text-slate-400 bg-slate-100 px-2 py-0.5 rounded-full">{{ $total }} students</span> <span class="text-xs font-medium text-slate-400 bg-slate-100 px-2 py-0.5 rounded-full">{{ $todayTotal }} students</span>
</div> </div>
<table class="w-full text-sm"> <table class="w-full text-sm">
<thead> <thead>
@@ -178,7 +178,7 @@
</tbody> </tbody>
</table> </table>
<div class="px-5 py-3 border-t border-slate-100 flex items-center justify-between"> <div class="px-5 py-3 border-t border-slate-100 flex items-center justify-between">
<p class="text-xs text-slate-400">Showing <span class="font-medium text-slate-600">{{ $total }}</span> students</p> <p class="text-xs text-slate-400">Showing <span class="font-medium text-slate-600">{{ $todayTotal }}</span> students</p>
</div> </div>
</div> </div>
</div> </div>
@@ -293,10 +293,10 @@
</div> </div>
<!-- Dev OTP preview --> <!-- Dev OTP preview -->
<div id="otpPreview" class="hidden bg-amber-50 border border-amber-200 rounded-xl px-4 py-2.5 mb-4 flex items-center gap-2"> {{-- <div id="otpPreview" class="hidden bg-amber-50 border border-amber-200 rounded-xl px-4 py-2.5 mb-4 flex items-center gap-2">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#d97706" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg> <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#d97706" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
<p class="text-xs font-semibold text-amber-700">Dev OTP: <span id="otpValue" class="font-mono tracking-widest"></span></p> <p class="text-xs font-semibold text-amber-700">Dev OTP: <span id="otpValue" class="font-mono tracking-widest"></span></p>
</div> </div> --}}
<label class="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wide">Enter OTP</label> <label class="block text-xs font-semibold text-slate-600 mb-1.5 uppercase tracking-wide">Enter OTP</label>
<input type="text" id="otp" maxlength="6" placeholder="— — — — — —" <input type="text" id="otp" maxlength="6" placeholder="— — — — — —"
@@ -527,10 +527,10 @@ function refreshPanel() {
el.innerHTML = '✕ Miss'; el.innerHTML = '✕ Miss';
} }
} else if (i === currentShot) { } else if (i === currentShot) {
el.classList.add('border-indigo-400', 'bg-indigo-50', 'text-indigo-600', 'animate-pulse'); el.classList.add('border-indigo-400', 'bg-indigo-50', 'text-indigo-600', 'animate-pulse', 'cursor-pointer');
el.innerHTML = 'Shot ' + i; el.innerHTML = 'Shot ' + i;
} else { } else {
el.classList.add('border-slate-200', 'bg-slate-50', 'text-slate-400'); el.classList.add('border-slate-200', 'bg-slate-50', 'text-slate-400', 'cursor-pointer');
el.innerHTML = 'Shot ' + i; el.innerHTML = 'Shot ' + i;
} }
} }
@@ -827,10 +827,10 @@ document.getElementById('sendOtpBtn').addEventListener('click', function () {
}) })
.done(res => { .done(res => {
document.getElementById('phoneSentTo').textContent = phone; document.getElementById('phoneSentTo').textContent = phone;
if (res.otp) { // if (res.otp) {
document.getElementById('otpPreview').classList.remove('hidden'); // document.getElementById('otpPreview').classList.remove('hidden');
document.getElementById('otpValue').textContent = res.otp; // document.getElementById('otpValue').textContent = res.otp;
} // }
goToStep(2); goToStep(2);
}) })
.fail(() => showError('Failed to send OTP. Please try again.')) .fail(() => showError('Failed to send OTP. Please try again.'))
+233
View File
@@ -0,0 +1,233 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Keep Climbing the Leaderboard!</title>
<style>
body{
margin:0;
padding:0;
background:#f5f8fc;
font-family:Arial, Helvetica, sans-serif;
color:#333333;
}
.wrapper{
width:100%;
padding:30px 15px;
}
.container{
max-width:600px;
margin:0 auto;
background:#ffffff;
border-radius:10px;
overflow:hidden;
box-shadow:0 3px 15px rgba(0,0,0,0.08);
}
.header{
background:#115AAA;
padding:35px 20px;
text-align:center;
}
.header h1{
margin:0;
color:#ffffff;
font-size:28px;
}
.content{
padding:35px 30px;
}
.greeting{
font-size:18px;
margin-bottom:20px;
}
.score-box{
background:#f0f8ff;
border-left:5px solid #00AEEF;
padding:20px;
text-align:center;
border-radius:8px;
margin:25px 0;
}
.score-label{
font-size:15px;
color:#666;
}
.score{
font-size:42px;
font-weight:bold;
color:#115AAA;
margin-top:10px;
}
.highlight{
color:#115AAA;
font-weight:bold;
}
.cta{
text-align:center;
margin:35px 0;
}
.btn{
display:inline-block;
padding:14px 32px;
background:#00AEEF;
color:#ffffff !important;
text-decoration:none;
border-radius:5px;
font-weight:bold;
}
.services{
background:#f7fbff;
padding:20px;
border-radius:8px;
margin-top:25px;
}
.services h3{
margin-top:0;
color:#115AAA;
}
.services ul{
padding-left:20px;
margin-bottom:0;
}
.footer{
background:#f4f7fa;
padding:25px;
text-align:center;
font-size:13px;
color:#777;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="container">
<div class="header">
<h1>Score & Win Challenge</h1>
</div>
<div style="background:#ffffff;padding:25px;text-align:center;">
<img src="https://rosettaeducation.com/storage/rosetta/files/1/rosetta-logo.png"
alt="Rosetta Education International"
style="max-width:200px;width:100%;height:auto;">
</div>
<div style="height:6px;background:#00AEEF;"></div>
<div class="content">
<div class="greeting">
Hi {{ $registration->name }},
</div>
<p>
Thank you for participating in today's <span class="highlight">Score & Win Challenge</span> at Rosetta Education Consultancy!
</p>
<div class="score-box">
<div class="score-label">Your Current Score</div>
<div class="score">{{ $registration->total_score}} Points</div>
</div>
<p>
Great job! Every point counts, and the competition is just getting started.
</p>
<p>
Remember, you can return <strong>tomorrow and play again</strong> to improve your score and climb higher on the leaderboard.
</p>
<p>
The participant with the highest score at the end of the campaign will be crowned the <strong>Ultimate Winner</strong> and receive exciting prizes and rewards.
</p>
<p>
The more days you play, the better your chances of winning.
</p>
<div class="services">
<h3>While You're Here, Explore Your Study Abroad Opportunities</h3>
<ul>
<li>Free education counseling & career guidance</li>
<li>Study destination support UK, Australia, Canada & USA</li>
<li>University selection tailored to your profile</li>
<li>SOP (Statement of Purpose) preparation</li>
<li>Scholarship assistance</li>
<li>Admission processing & documentation support
</li>
<li>Student visa guidance</li>
<li>IELTS & PTE preparation classes</li>
</ul>
</div>
<p style="margin-top:25px;">
We look forward to seeing you again tomorrow. Keep playing, keep scoring, and keep moving closer to your dream destination.
</p>
</div>
<div style="
margin-top:40px;
padding:25px;
border-top:1px solid #e5e5e5;
">
<p style="margin:0 0 15px 0;">
Best Regards,
</p>
<img src="https://rosettaeducation.com/storage/rosetta/files/1/rosetta-logo.png"
alt="Rosetta Education International"
style="max-width:200px;height:auto;margin-bottom:15px;">
<p style="margin:0;font-weight:bold;color:#115AAA;">
Rosetta Education International
</p>
<p style="margin:8px 0;color:#666;font-size:14px;">
Your Trusted Study Abroad Partner
</p>
<p style="margin:5px 0;color:#666;font-size:14px;">
📧 info@rosettaeducation.com
</p>
<p style="margin:5px 0;color:#666;font-size:14px;">
📞 +977-1-4560413
</p>
<p style="margin:5px 0;color:#666;font-size:14px;">
🌐 https://rosettaeducation.com/
</p>
<div class="footer">
© 2026 Rosetta Education Consultancy<br>
Helping Students Reach Global Opportunities
</div>
</div>
</div>
</body>
</html>
+211
View File
@@ -0,0 +1,211 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to Score & Win</title>
<style>
body{
margin:0;
padding:0;
background:#f5f8fc;
font-family:Arial, Helvetica, sans-serif;
color:#333333;
}
.wrapper{
width:100%;
padding:30px 15px;
}
.container{
max-width:600px;
margin:0 auto;
background:#ffffff;
border-radius:10px;
overflow:hidden;
box-shadow:0 3px 15px rgba(0,0,0,0.08);
}
.header{
background:#115AAA;
padding:35px 20px;
text-align:center;
}
.header h1{
margin:0;
color:#ffffff;
font-size:28px;
}
.content{
padding:35px 30px;
}
.welcome-box{
background:#f0f8ff;
border-left:5px solid #00AEEF;
padding:20px;
border-radius:8px;
margin:25px 0;
}
.cta{
text-align:center;
margin:35px 0;
}
.btn{
display:inline-block;
padding:14px 32px;
background:#00AEEF;
color:#ffffff !important;
text-decoration:none;
border-radius:5px;
font-weight:bold;
}
.services{
background:#f7fbff;
padding:20px;
border-radius:8px;
margin-top:25px;
}
.services h3{
margin-top:0;
color:#115AAA;
}
.footer{
background:#f4f7fa;
padding:25px;
text-align:center;
font-size:13px;
color:#777;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="container">
<div class="header">
<h1>Welcome to Score & Win</h1>
</div>
<div style="background:#ffffff;padding:25px;text-align:center;">
<img src="https://rosettaeducation.com/storage/rosetta/files/1/rosetta-logo.png"
alt="Rosetta Education International"
style="max-width:200px;width:100%;height:auto;">
</div>
<div class="content">
<h2 style="color:#115AAA;">Hi {{$registration->name}},</h2>
<p>
Welcome to the exciting <strong>Score & Win Challenge</strong> by Rosetta Education Consultancy.
</p>
<div class="welcome-box">
<strong>Your registration has been successfully completed.</strong>
<br><br>
You're now officially part of the competition and eligible to compete for exciting prizes.
</div>
<p>
Here's how it works:
</p>
<ul>
<li>Free education counseling & career guidance</li>
<li>Study destination support UK, Australia, Canada & USA</li>
<li>University selection tailored to your profile</li>
<li>SOP (Statement of Purpose) preparation</li>
<li>Scholarship assistance</li>
<li>Admission processing & documentation support
</li>
<li>Student visa guidance</li>
<li>IELTS & PTE preparation classes</li>
</ul>
<p>
Consistent participation increases your chances of securing a top position and winning amazing rewards.
</p>
<div class="services">
<h3>Why Students Choose Rosetta Education Consultancy</h3>
<ul>
<li>Expert Study Abroad Counseling</li>
<li>University Application Assistance</li>
<li>Scholarship & Financial Guidance</li>
<li>Visa Processing Support</li>
<li>Career-Oriented Course Selection</li>
<li>End-to-End Student Assistance</li>
</ul>
</div>
<p style="margin-top:25px;">
While you're competing for prizes, don't forget that our team is here to help you achieve your international education goals.
</p>
<p>
Best of luck, and let the challenge begin!
</p>
</div>
<div style="
margin-top:40px;
padding:25px;
border-top:1px solid #e5e5e5;
">
<p style="margin:0 0 15px 0;">
Best Regards,
</p>
<img src="https://rosettaeducation.com/storage/rosetta/files/1/rosetta-logo.png"
alt="Rosetta Education International"
style="max-width:200px;height:auto;margin-bottom:15px;">
<p style="margin:0;font-weight:bold;color:#115AAA;">
Rosetta Education International
</p>
<p style="margin:8px 0;color:#666;font-size:14px;">
Your Trusted Study Abroad Partner
</p>
<p style="margin:5px 0;color:#666;font-size:14px;">
📧 info@rosettaeducation.com
</p>
<p style="margin:5px 0;color:#666;font-size:14px;">
📞 +977-1-4560413
</p>
<p style="margin:5px 0;color:#666;font-size:14px;">
🌐 https://rosettaeducation.com/
</p>
</div>
<div class="footer">
© 2026 Rosetta Education Consultancy<br>
Your Trusted Study Abroad Partner
</div>
</div>
</div>
</body>
</html>
+1 -1
View File
@@ -295,7 +295,7 @@
} }
p.shots.forEach((result, i) => { p.shots.forEach((result, i) => {
if (lastShots[i] === undefined) renderShot(i, result); if (lastShots[i] === undefined || lastShots[i] !== result) renderShot(i, result);
}); });
lastShots = [...p.shots]; lastShots = [...p.shots];