Notify super admins on new user registration (bell + email)

When a new user registers, all super_admin users receive:
- A database notification shown in the bell (type: new_user), with the
  new user's avatar and a link to their channel
- An email congratulating them with the new member's name, email,
  join date, gender, and nationality, plus a View Profile CTA

Notification rendering in app.blade.php refactored into notifHref() and
notifThumb() helpers so new_user notifications link to /channel/{slug}
and show a circular avatar instead of a video thumbnail. Also fixed the
legacy /storage/thumbnails/ path to /media/thumbnails/ for video notifications.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ghassan 2026-05-16 23:34:28 +03:00
parent 4887d0c517
commit 3fe167e33f
4 changed files with 116 additions and 6 deletions

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Notifications\NewUserRegistered;
use App\Rules\NotDisposableEmail;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\Request;
@ -45,6 +46,11 @@ class RegisteredUserController extends Controller
event(new Registered($user));
// Notify all super admins of the new registration
User::where('role', 'super_admin')->each(function (User $admin) use ($user) {
$admin->notify(new NewUserRegistered($user));
});
auth()->login($user);
return redirect()->route('verification.notice');

View File

@ -0,0 +1,39 @@
<?php
namespace App\Notifications;
use App\Models\User;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;
class NewUserRegistered extends Notification
{
public function __construct(public User $newUser) {}
public function via(object $notifiable): array
{
return ['database', 'mail'];
}
public function toArray(object $notifiable): array
{
return [
'type' => 'new_user',
'user_id' => $this->newUser->id,
'user_name' => $this->newUser->name,
'user_email' => $this->newUser->email,
'user_avatar' => $this->newUser->avatar_url,
'user_channel' => $this->newUser->channel,
];
}
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
->subject('New member joined TAKEONE 🎉')
->view('emails.new-user-registered', [
'newUser' => $this->newUser,
'admin' => $notifiable,
]);
}
}

View File

@ -0,0 +1,51 @@
<x-emails.layout subject="New member joined TAKEONE">
{{-- Icon --}}
<div style="width:64px;height:64px;border-radius:50%;background:rgba(230,30,30,.12);border:1px solid rgba(230,30,30,.25);margin:0 auto 24px;text-align:center;line-height:64px;font-size:28px;">&#x1F389;</div>
<h1 class="email-title">New member joined!</h1>
<p class="email-subtitle">Someone just signed up to TAKEONE.</p>
<p class="email-text">Hi <strong style="color:#f1f1f1;">{{ $admin->name }}</strong>,</p>
<p class="email-text">
Congratulations a new member has just joined the platform! Here are their details:
</p>
{{-- Info box --}}
<div class="email-infobox">
<div class="email-infobox-label">New Member Details</div>
<div class="email-inforow">
<span class="email-inforow-key">Name</span>
<span class="email-inforow-val">{{ $newUser->name }}</span>
</div>
<div class="email-inforow">
<span class="email-inforow-key">Email</span>
<span class="email-inforow-val">{{ $newUser->email }}</span>
</div>
<div class="email-inforow">
<span class="email-inforow-key">Joined</span>
<span class="email-inforow-val">{{ $newUser->created_at->format('d M Y, H:i') }}</span>
</div>
<div class="email-inforow">
<span class="email-inforow-key">Gender</span>
<span class="email-inforow-val" style="text-transform:capitalize;">{{ $newUser->gender ?? '—' }}</span>
</div>
<div class="email-inforow">
<span class="email-inforow-key">Nationality</span>
<span class="email-inforow-val">{{ $newUser->nationality ?? '—' }}</span>
</div>
</div>
{{-- CTA --}}
<div class="email-btn-wrap">
<a href="{{ route('channel', $newUser->channel) }}" class="email-btn">&#x1F464;&nbsp; View Profile</a>
</div>
<hr class="email-divider">
<p class="email-note">
You are receiving this because you are a super admin on
<a href="{{ config('app.url') }}">{{ config('app.name') }}</a>.
</p>
</x-emails.layout>

View File

@ -1219,17 +1219,34 @@
}
function notifText(d) {
var actor = '<strong>' + escHtml(d.actor_name || d.uploader_name || '') + '</strong>';
var actor = '<strong>' + escHtml(d.actor_name || d.uploader_name || d.user_name || '') + '</strong>';
var title = '"' + escHtml(d.video_title || '') + '"';
var preview = d.comment_preview ? ' <em class="yt-notif-preview">"' + escHtml(d.comment_preview) + '"</em>' : '';
switch (d.type) {
case 'new_comment': return actor + ' commented on your video ' + title + ':' + preview;
case 'new_reply': return actor + ' replied to your comment:' + preview;
case 'comment_like':return actor + ' liked your comment on ' + title;
case 'new_user': return '&#x1F389; ' + actor + ' just joined TAKEONE!';
default: return actor + ' uploaded a new video: ' + title;
}
}
function notifHref(d) {
if (d.type === 'new_user') return '/channel/' + encodeURIComponent(d.user_channel || '');
return '/videos/' + escHtml(d.video_route_key || '');
}
function notifThumb(d) {
if (d.type === 'new_user') {
return d.user_avatar
? '<img class="yt-notif-thumb" src="' + escHtml(d.user_avatar) + '" alt="" loading="lazy" onerror="notifThumbFallback(this)" style="border-radius:50%">'
: '<div class="yt-notif-thumb-placeholder"><i class="bi bi-person-circle"></i></div>';
}
return d.video_thumbnail
? '<img class="yt-notif-thumb" src="/media/thumbnails/' + escHtml(d.video_thumbnail) + '" alt="" loading="lazy" onerror="notifThumbFallback(this)">'
: '<div class="yt-notif-thumb-placeholder"><i class="bi bi-play-circle"></i></div>';
}
function renderNotifications(data) {
var unread = data.unread_count;
if (unread > 0) {
@ -1248,15 +1265,12 @@
list.innerHTML = data.notifications.map(function (n) {
var d = n.data;
var thumb = d.video_thumbnail
? '<img class="yt-notif-thumb" src="/storage/thumbnails/' + escHtml(d.video_thumbnail) + '" alt="" loading="lazy" onerror="notifThumbFallback(this)">'
: '<div class="yt-notif-thumb-placeholder"><i class="bi bi-play-circle"></i></div>';
var dot = !n.read ? '<div class="yt-notif-dot"></div>' : '';
return '<a class="yt-notif-item' + (!n.read ? ' unread' : '') + '" '
+ 'href="/videos/' + escHtml(d.video_route_key) + '" '
+ 'href="' + notifHref(d) + '" '
+ 'data-notif-id="' + escHtml(n.id) + '" '
+ 'onclick="handleNotifClick(event, this)">'
+ thumb
+ notifThumb(d)
+ '<div class="yt-notif-body">'
+ '<div class="yt-notif-text">' + notifText(d) + '</div>'
+ '<div class="yt-notif-time">' + escHtml(n.time) + '</div>'