From 250509055b7b7e3b84817e73ffb6f603bf71b010 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 25 May 2026 09:55:47 +0200 Subject: [PATCH] Finish Notifications -> Conversation --- .../Controllers/DynamicLoadController.php | 7 ++ app/Services/XenforoApiService.php | 12 +- extra.less | 2 +- resources/css/components/notifications.css | 2 +- resources/js/app.js | 4 + resources/js/conversations.js | 103 ++++++++++++++++++ .../js/types/ConversationResponseItem.js | 29 +++++ .../views/components/conversations.blade.php | 68 ++++++++++++ .../views/components/notifications.blade.php | 2 +- resources/views/components/topbar.blade.php | 16 ++- routes/web.php | 1 + 11 files changed, 238 insertions(+), 8 deletions(-) create mode 100644 resources/js/conversations.js create mode 100644 resources/js/types/ConversationResponseItem.js create mode 100644 resources/views/components/conversations.blade.php diff --git a/app/Http/Controllers/DynamicLoadController.php b/app/Http/Controllers/DynamicLoadController.php index 7f99545..4e8b505 100644 --- a/app/Http/Controllers/DynamicLoadController.php +++ b/app/Http/Controllers/DynamicLoadController.php @@ -55,4 +55,11 @@ class DynamicLoadController extends Controller return response()->json( ['success' => true] ); } + + public function getConversations( Request $request ){ + $service = app(XenforoApiService::class); + $data = $service->getConversations(\Auth::user()->user_id); + + return response()->json( $data ); + } } diff --git a/app/Services/XenforoApiService.php b/app/Services/XenforoApiService.php index 041c698..aa51595 100644 --- a/app/Services/XenforoApiService.php +++ b/app/Services/XenforoApiService.php @@ -49,8 +49,9 @@ class XenforoApiService { public function getUserAlerts(int $userId): mixed { - if( app(XenforoService::class)->getXfUser($userId)?->alerts_unviewed > 0 ) - return $this->get("alerts?page=1&cutoff=7days", $userId); + if( app(XenforoService::class)->getXfUser($userId)?->alerts_unviewed > 0 ) { + Cache::forget("xf_alerts_{$userId}"); + } return Cache::remember("xf_alerts_{$userId}", 60, function() use($userId) { return $this->get("alerts?page=1&cutoff=7days", $userId); @@ -63,4 +64,11 @@ class XenforoApiService { $this->post("alerts/marl-all", $userId ); } + public function getConversations(int $userId): mixed + { + return Cache::remember("xf_conversations_{$userId}", 60, function() use($userId) { + return $this->get("conversations?page=1&receiver_id={$userId}", $userId); + }); + } + } diff --git a/extra.less b/extra.less index cf5ceaf..b3ee9b8 100644 --- a/extra.less +++ b/extra.less @@ -1554,7 +1554,7 @@ ul { /* File: resources/css/components/notifications.css */ -.\$notifications { +.\$notifications, .\$conversations { position: absolute; top: calc(100% + 8px); right: 0; diff --git a/resources/css/components/notifications.css b/resources/css/components/notifications.css index bfb9a11..728141c 100644 --- a/resources/css/components/notifications.css +++ b/resources/css/components/notifications.css @@ -1,4 +1,4 @@ -.notifications { +.notifications, .conversations { position: absolute; top: calc(100% + 8px); right: 0; diff --git a/resources/js/app.js b/resources/js/app.js index 4b7891d..cd97947 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -5,6 +5,7 @@ import "easymde/dist/easymde.min.css"; import { calculate as calculateHashes } from "./hashes.js"; import hovercard from "./hovercard.js"; import notifications from "./notifications.js"; +import conversations from "./conversations.js"; // Lucide icons. @@ -27,3 +28,6 @@ Alpine.store('hovercard', hovercard() ); // Notifications Alpine.store('notifications', notifications() ); + +// Conversations +Alpine.store('conversations', conversations() ); diff --git a/resources/js/conversations.js b/resources/js/conversations.js new file mode 100644 index 0000000..ddfb077 --- /dev/null +++ b/resources/js/conversations.js @@ -0,0 +1,103 @@ +/** @typedef { import('types/ConversationResponseItem.js').ConversationResponseItem} ConversationResponseItem */ + +export default function conversations() { + return { + + /** + * @type {boolean} + */ + start: false, + + /** + * @type {ConversationResponseItem[]} + */ + data: null, + + /** + * @type {boolean} + */ + loading: false, + + /** + * @type {boolean} + */ + error: false, + + /** + * @type {number} + */ + unviewed: 0, + + /** + * Request for getting notifications. + * + * @return {Promise} + */ + async getConversations() { + if( this.loading ) + return; + + this.loading = true; + this.error = false; + + try { + const RESPONSE = await fetch('/api/dynamic/conversations', { credentials: "include", headers: { 'X-Requested-With': 'XMLHttpRequest' } }); + + if( !RESPONSE.ok ) + throw new Error(RESPONSE.status) + + let json = await RESPONSE.json() + if( !json.conversations ) + throw new Error(RESPONSE.status); + + this.data = json.conversations; + + + } catch (error) { + this.error = true; + } finally { + this.loading = false; + } + }, + + /** + * + * @param {HTMLElement} anchorEl + * @return {Promise} + */ + async open( anchorEl ){ + + if( this.start ){ + this.close(); + return; + } + + this.start = !this.start; + if( this.start && !this.data ){ + await this.getConversations(); + } + + if( this.start ){ + Alpine.nextTick(() => window.refreshIcons(document.querySelector('.notifications'))) + } + }, + + /** + * @return {number} + */ + get unread(){ + if( !this.data ){ + return this.unviewed; + } + return this.data.filter(a => a.is_unread === 0 ).length; + }, + + /** + * + */ + close(){ + this.start = false; + } + + } +} diff --git a/resources/js/types/ConversationResponseItem.js b/resources/js/types/ConversationResponseItem.js new file mode 100644 index 0000000..de5ac3d --- /dev/null +++ b/resources/js/types/ConversationResponseItem.js @@ -0,0 +1,29 @@ +/** + * @typedef {Object} ConversationResponseItem + * + * @see app/Http/DynamicLoadController.php + * + * @property {string} username + * @property {object} recipients + * @property {boolean} is_starred + * @property {boolean} is_unread + * @property {boolean} can_edit + * @property {boolean} can_reply + * @property {boolean} can_invite + * @property {boolean} can_upload_attachment + * @property {boolean} view_url + * @property {number} conversation_id + * @property {string} title + * @property {number} user_id + * @property {number} start_date + * @property {boolean} open_invite + * @property {boolean} conversation_open + * @property {number} reply_count + * @property {number} recipient_count + * @property {number} first_message_id + * @property {number} last_message_date + * @property {number} last_message_id + * @property {number} last_message_user_id + * @property {Object} Starter + */ +export {} diff --git a/resources/views/components/conversations.blade.php b/resources/views/components/conversations.blade.php new file mode 100644 index 0000000..4424c3c --- /dev/null +++ b/resources/views/components/conversations.blade.php @@ -0,0 +1,68 @@ +
+
+ Private Messages + +
+ + + + + + +
diff --git a/resources/views/components/notifications.blade.php b/resources/views/components/notifications.blade.php index 294974c..bb02ca9 100644 --- a/resources/views/components/notifications.blade.php +++ b/resources/views/components/notifications.blade.php @@ -14,7 +14,7 @@ - + diff --git a/resources/views/components/topbar.blade.php b/resources/views/components/topbar.blade.php index 2dbde60..3e4e00f 100644 --- a/resources/views/components/topbar.blade.php +++ b/resources/views/components/topbar.blade.php @@ -62,9 +62,19 @@ @include('components.notifications') - +
+ + + @include('components.conversations') +
@endif