Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/Http/Controllers/ChatController.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ public function index(Request $request)
{
$user = $request->user();

if ($user) {
$user->updateQuietly(['chat_last_seen_at' => now()]);
}

// Get Chat Config Status
$isEnabled = AppSetting::get('global_chat_enabled', true);
$audience = AppSetting::get('global_chat_audience', 'verified_members'); // 'verified_members', 'all', 'disabled'
Expand Down
10 changes: 8 additions & 2 deletions app/Http/Controllers/NotificationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ class NotificationController extends Controller
*/
public function index(Request $request): JsonResponse
{
$perPage = min(max((int) $request->query('per_page', 10), 5), 50);
$page = (int) $request->query('page', 1);

if ($page === 1) {
$request->user()->updateQuietly([
'notifications_last_seen_at' => now(),
]);
}

$paginator = $request->user()
->notifications()
->latest()
->simplePaginate($perPage);
->simplePaginate(10);

$notifications = collect($paginator->items())->map(function ($notification) {
return [
Expand Down
24 changes: 22 additions & 2 deletions app/Http/Middleware/HandleInertiaRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Http\Middleware;

use App\Models\ChatMessage;
use Illuminate\Http\Request;
use Inertia\Middleware;

Expand Down Expand Up @@ -35,13 +36,32 @@ public function version(Request $request): ?string
*/
public function share(Request $request): array
{
$user = $request->user();

if ($user && (! $user->last_active_at || $user->last_active_at->diffInMinutes(now()) >= 5)) {
$user->updateQuietly(['last_active_at' => now()]);
$user->last_active_at = now();
}

if ($user && str_contains((string) $request->header('referer', ''), '/chat')) {
$user->updateQuietly(['chat_last_seen_at' => now()]);
$user->chat_last_seen_at = now();
}

return [
...parent::share($request),
'name' => config('app.name'),
'app_version' => config('app.version'),
'auth' => [
'user' => $request->user(),
'unread_notifications_count' => $request->user()?->unreadNotifications()->count() ?? 0,
'user' => $user,
'unread_notifications_count' => $request->user()?->unreadNotifications()->when($request->user()?->notifications_last_seen_at, fn ($q, $seen) => $q->where('created_at', '>', $seen))->take(10)->count() ?? 0,
'unread_chat_messages_count' => $request->user()
? ChatMessage::where('user_id', '!=', $request->user()->id)
->when(
$request->user()->chat_last_seen_at,
fn ($q, $seen) => $q->where('created_at', '>', $seen)
)->take(10)->count()
: 0,
'can_access_admin' => $request->user()?->can('view admin') ?? false,
'permissions' => $request->user()?->getAllPermissions()->pluck('name')->toArray() ?? [],
],
Expand Down
11 changes: 11 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ class User extends Authenticatable
'facebook',
'instagram',
'github',
'notifications_last_seen_at',
'chat_last_seen_at',
'last_active_at',
];

protected static function booted(): void
Expand All @@ -73,6 +76,11 @@ protected static function booted(): void
'is_banned',
];

public function isOnline(): bool
{
return $this->last_active_at && $this->last_active_at->greaterThan(now()->subMinutes(5));
}

public function isBanned(): bool
{
return $this->banned_until !== null && $this->banned_until->isFuture();
Expand Down Expand Up @@ -104,6 +112,9 @@ protected function casts(): array
return [
'email_verified_at' => 'datetime',
'banned_until' => 'datetime',
'notifications_last_seen_at' => 'datetime',
'chat_last_seen_at' => 'datetime',
'last_active_at' => 'datetime',
'password' => 'hashed',
'receive_emails' => 'boolean',
'is_verified' => 'boolean',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->timestamp('notifications_last_seen_at')->nullable()->after('remember_token');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('notifications_last_seen_at');
});
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->timestamp('chat_last_seen_at')->nullable()->after('notifications_last_seen_at');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('chat_last_seen_at');
});
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->timestamp('last_active_at')->nullable()->index()->after('remember_token');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('last_active_at');
});
}
};
44 changes: 10 additions & 34 deletions resources/js/components/NotificationDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const fetchNotifications = async () => {
currentPage.value = 1;

try {
const res = await fetch('/notifications?per_page=10&page=1', {
const res = await fetch('/notifications?page=1', {
headers: {
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
Expand All @@ -86,10 +86,6 @@ const fetchNotifications = async () => {
notifications.value = data.notifications || [];
hasMore.value = Boolean(data.has_more);
currentPage.value = data.current_page || 1;

if (typeof data.unread_count === 'number') {
unreadCount.value = data.unread_count;
}
}
} catch (e) {
console.error('Failed to fetch notifications:', e);
Expand All @@ -107,7 +103,7 @@ const loadMore = async () => {
const nextPage = currentPage.value + 1;

try {
const res = await fetch(`/notifications?per_page=10&page=${nextPage}`, {
const res = await fetch(`/notifications?page=${nextPage}`, {
headers: {
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
Expand All @@ -120,10 +116,6 @@ const loadMore = async () => {
notifications.value = [...notifications.value, ...newItems];
hasMore.value = Boolean(data.has_more);
currentPage.value = data.current_page || nextPage;

if (typeof data.unread_count === 'number') {
unreadCount.value = data.unread_count;
}
}
} catch (e) {
console.error('Failed to load more notifications:', e);
Expand Down Expand Up @@ -154,6 +146,12 @@ const toggleDropdown = () => {
isOpen.value = !isOpen.value;

if (isOpen.value) {
unreadCount.value = 0;

if (page.props.auth) {
(page.props.auth as any).unread_notifications_count = 0;
}

updatePanelPosition();
fetchNotifications();
}
Expand Down Expand Up @@ -211,14 +209,6 @@ const markSingleAsRead = async (notification: NotificationItem, e?: Event) => {
// Optimistically mark as read in local state
notification.read_at = new Date().toISOString();

if (unreadCount.value > 0) {
unreadCount.value--;
}

if (page.props.auth) {
(page.props.auth as any).unread_notifications_count = unreadCount.value;
}

try {
await fetch(`/notifications/${notification.id}/read`, {
method: 'POST',
Expand All @@ -239,15 +229,6 @@ const markAsRead = async (notification: NotificationItem) => {
// Optimistically mark as read in local state
notification.read_at = new Date().toISOString();

if (unreadCount.value > 0) {
unreadCount.value--;
}

if (page.props.auth) {
(page.props.auth as any).unread_notifications_count =
unreadCount.value;
}

try {
await fetch(`/notifications/${notification.id}/read`, {
method: 'POST',
Expand All @@ -271,7 +252,7 @@ const markAsRead = async (notification: NotificationItem) => {
};

const markAllAsRead = async () => {
if (isMarkingAll.value || unreadCount.value === 0) {
if (isMarkingAll.value || !notifications.value.some((n) => !n.read_at)) {
return;
}

Expand All @@ -282,11 +263,6 @@ const markAllAsRead = async () => {
n.read_at = new Date().toISOString();
}
});
unreadCount.value = 0;

if (page.props.auth) {
(page.props.auth as any).unread_notifications_count = 0;
}

try {
await fetch('/notifications/mark-all-read', {
Expand Down Expand Up @@ -437,7 +413,7 @@ onBeforeUnmount(() => {

<div class="flex items-center gap-2.5">
<button
v-if="unreadCount > 0"
v-if="notifications.some((n) => !n.read_at)"
@click="markAllAsRead"
:disabled="isMarkingAll"
type="button"
Expand Down
Loading
Loading