diff --git a/TODO.md b/TODO.md index 322a075..a799abe 100644 --- a/TODO.md +++ b/TODO.md @@ -41,4 +41,5 @@ - [x] ubah filter di mitra list dan customer list menjadi seperti di sale index - [x] untuk detail mitra nanti akan ada button untuk (transaksi mitra dengan cakupan: pembelian voucher, pembayaran hutang, topuplimit, penambahan batas bayar, history deposit) -- [ ] di dashboard tambahkan list deposit terbaru +- [ ] notifikasi deposit +- [ ] notifikasi lokasi voucher tipis diff --git a/app/Events/NotificationEvent.php b/app/Events/NotificationEvent.php new file mode 100644 index 0000000..d04ee6d --- /dev/null +++ b/app/Events/NotificationEvent.php @@ -0,0 +1,34 @@ + $message, 'info' => 'triggered']); + } + + /** + * Get the channels the event should broadcast on. + * + * @return array + */ + public function broadcastOn() + { + return new Channel('notification'); + } +} diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 5c113b2..2bdafef 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -4,6 +4,7 @@ namespace App\Http\Middleware; use App\Models\Notification; use Illuminate\Http\Request; +use Illuminate\Notifications\Notification as NotificationsNotification; use Inertia\Middleware; class HandleInertiaRequests extends Middleware @@ -30,6 +31,7 @@ class HandleInertiaRequests extends Middleware */ public function share(Request $request): array { + $notifications = Notification::where('entity_type', \App\Models\User::class)->orderBy('created_at', 'desc'); return array_merge(parent::share($request), [ 'auth' => [ 'user' => $request->user() ? $request->user()->load(['role.permissions']) : $request->user(), @@ -39,8 +41,10 @@ class HandleInertiaRequests extends Middleware ], 'app_name' => env('APP_NAME', 'App Name'), 'csrf_token' => csrf_token(), - 'notifications' => Notification::where('entity_type', \App\Models\User::class)->orderBy('created_at', 'desc')->limit(10)->get(), - 'count_unread_notifications' => Notification::where('entity_type', \App\Models\User::class)->where('is_read', Notification::UNREAD)->count(), + 'notifications' => $notifications->limit(10)->get(), + 'count_unread_notifications' => $notifications->where('is_read', Notification::UNREAD)->count(), + 'deposit_notifications' => $notifications->where('type', Notification::TYPE_DEPOSIT) + ->where('is_read', Notification::UNREAD)->limit(10)->get(), ]); } } diff --git a/app/Models/DepositHistory.php b/app/Models/DepositHistory.php index 50c130d..23122a2 100644 --- a/app/Models/DepositHistory.php +++ b/app/Models/DepositHistory.php @@ -2,9 +2,11 @@ namespace App\Models; +use App\Events\NotificationEvent; use App\Services\GeneralService; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Support\Carbon; +use PHPUnit\Framework\Error\Notice; class DepositHistory extends Model { @@ -99,10 +101,10 @@ class DepositHistory extends Model { return Attribute::make(get: function () { if ($this->credit == 0) { - return 'Rp '.number_format($this->debit, is_float($this->debit) ? 2 : 0, ',', '.'); + return 'Rp ' . number_format($this->debit, is_float($this->debit) ? 2 : 0, ',', '.'); } - return '-Rp '.number_format($this->credit, is_float($this->credit) ? 2 : 0, ',', '.'); + return '-Rp ' . number_format($this->credit, is_float($this->credit) ? 2 : 0, ',', '.'); }); } @@ -163,9 +165,12 @@ class DepositHistory extends Model if ($this->is_valid == self::STATUS_WAIT_APPROVE) { $status = ' (bukti bayar di upload, membutuhkan konfirmasi)'; } - Notification::create([ + + $notification = Notification::create([ 'entity_type' => User::class, - 'description' => $this->customer->fullname.' melakukan deposit transfer manual sebesar : '.$this->amount.$status, + 'description' => $this->customer->fullname . ' melakukan deposit transfer manual sebesar : ' . $this->amount . $status, + 'url' => route('deposit.edit', $this), + 'type' => Notification::TYPE_DEPOSIT, ]); } @@ -174,25 +179,43 @@ class DepositHistory extends Model if ($this->is_valid == self::STATUS_WAIT_APPROVE) { $status = ' (bukti bayar di upload, membutuhkan konfirmasi)'; } - Notification::create([ + + $notification = Notification::create([ 'entity_type' => User::class, - 'description' => $this->customer->fullname.' melakukan deposit manual sebesar : '.$this->amount.$status, + 'description' => $this->customer->fullname . ' melakukan deposit manual sebesar : ' . $this->amount . $status, + 'url' => route('deposit.edit', $this), + 'type' => Notification::TYPE_DEPOSIT, ]); } if ($this->payment_channel == Setting::PAYMENT_MIDTRANS) { - Notification::create([ + $notification = Notification::create([ 'entity_type' => User::class, - 'description' => $this->customer->fullname.' melakukan deposit via midtrans sebesar : '.$this->amount, + 'description' => $this->customer->fullname . ' melakukan deposit via midtrans sebesar : ' . $this->amount, + 'url' => route('deposit.edit', $this), + 'type' => Notification::TYPE_DEPOSIT, ]); } + + NotificationEvent::dispatch([ + 'id' => $notification->id, + 'description' => $notification->description, + 'url' => $notification->url, + 'type' => Notification::TYPE_DEPOSIT, + 'format_created_at' => now()->translatedFormat('d F Y H:i:s'), + 'deposit_notifications' => Notification::where('entity_type', User::class) + ->where('type', Notification::TYPE_DEPOSIT) + ->where('is_read', Notification::UNREAD)->limit(10) + ->orderBy('created_at', 'desc') + ->get(), + ]); } public function create_notification_user() { Notification::create([ 'entity_id' => $this->customer_id, - 'description' => 'Deposit '.$this->description.' sebesar '.$this->amount.' sudah sukses diterima', + 'description' => 'Deposit ' . $this->description . ' sebesar ' . $this->amount . ' sudah sukses diterima', ]); } } diff --git a/app/Models/Notification.php b/app/Models/Notification.php index cb59b75..9447627 100644 --- a/app/Models/Notification.php +++ b/app/Models/Notification.php @@ -7,6 +7,8 @@ use Illuminate\Support\Carbon; class Notification extends Model { + const TYPE_DEPOSIT = 'deposit'; + const UNREAD = 0; const READ = 1; @@ -16,6 +18,8 @@ class Notification extends Model 'entity_id', 'description', 'is_read', + 'url', + 'type', ]; protected $appends = [ diff --git a/composer.json b/composer.json index 32899fd..9c494c2 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "laravel/socialite": "^5.6.3", "laravel/tinker": "^2.8.1", "midtrans/midtrans-php": "^2.5.2", + "pusher/pusher-php-server": "^7.2", "react/async": "^4.1", "socialiteproviders/google": "^4.1", "tightenco/ziggy": "^1.6.0" diff --git a/composer.lock b/composer.lock index dd8d30d..a1079f0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "db8739a9b80108312e32b7bcb4417196", + "content-hash": "4bf9a351b59a33c5437ac6825a68804c", "packages": [ { "name": "brick/math", @@ -2573,6 +2573,142 @@ ], "time": "2023-02-08T01:06:31+00:00" }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "paragonie/sodium_compat", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/e592a3e06d1fa0d43988c7c7d9948ca836f644b6", + "reference": "e592a3e06d1fa0d43988c7c7d9948ca836f644b6", + "shasum": "" + }, + "require": { + "paragonie/random_compat": ">=1", + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^3|^4|^5|^6|^7|^8|^9" + }, + "suggest": { + "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", + "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "keywords": [ + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" + ], + "support": { + "issues": "https://github.com/paragonie/sodium_compat/issues", + "source": "https://github.com/paragonie/sodium_compat/tree/v1.20.0" + }, + "time": "2023-04-30T00:54:53+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.1", @@ -3088,6 +3224,67 @@ }, "time": "2023-05-23T02:31:11+00:00" }, + { + "name": "pusher/pusher-php-server", + "version": "7.2.3", + "source": { + "type": "git", + "url": "https://github.com/pusher/pusher-http-php.git", + "reference": "416e68dd5f640175ad5982131c42a7a666d1d8e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/416e68dd5f640175ad5982131c42a7a666d1d8e9", + "reference": "416e68dd5f640175ad5982131c42a7a666d1d8e9", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "guzzlehttp/guzzle": "^7.2", + "paragonie/sodium_compat": "^1.6", + "php": "^7.3|^8.0", + "psr/log": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "overtrue/phplint": "^2.3", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "psr-4": { + "Pusher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Library for interacting with the Pusher REST API", + "keywords": [ + "events", + "messaging", + "php-pusher-server", + "publish", + "push", + "pusher", + "real time", + "real-time", + "realtime", + "rest", + "trigger" + ], + "support": { + "issues": "https://github.com/pusher/pusher-http-php/issues", + "source": "https://github.com/pusher/pusher-http-php/tree/7.2.3" + }, + "time": "2023-05-17T16:00:06+00:00" + }, { "name": "ralouphie/getallheaders", "version": "3.0.3", diff --git a/config/broadcasting.php b/config/broadcasting.php index 9e4d4aa..81d09d1 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -36,10 +36,10 @@ return [ 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ - 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', + 'host' => env('PUSHER_HOST') ?: 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com', 'port' => env('PUSHER_PORT', 443), 'scheme' => env('PUSHER_SCHEME', 'https'), - 'encrypted' => true, + 'encrypted' => false, 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', ], 'client_options' => [ diff --git a/database/migrations/2023_05_24_130521_create_notifications_table.php b/database/migrations/2023_05_24_130521_create_notifications_table.php index 6a2ad36..c4155f9 100644 --- a/database/migrations/2023_05_24_130521_create_notifications_table.php +++ b/database/migrations/2023_05_24_130521_create_notifications_table.php @@ -18,6 +18,8 @@ return new class extends Migration $table->ulid('entity_id')->nullable(); $table->string('description')->nullable(); $table->smallInteger('is_read')->default(0); + $table->string('url')->nullable(); + $table->string('type')->nullable(); $table->timestamps(); $table->softDeletes(); diff --git a/package-lock.json b/package-lock.json index 5598f38..7e6999c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "flowbite": "^1.6.6", "flowbite-react": "^0.4.9", "is": "^3.3.0", + "lodash": "^4.17.19", "moment": "^2.29.4", "nprogress": "^0.2.0", "nuka-carousel": "^6.0.3", @@ -30,12 +31,13 @@ "@vitejs/plugin-react": "^3.0.0", "autoprefixer": "^10.4.12", "axios": "^1.1.2", - "laravel-vite-plugin": "^0.7.2", - "lodash": "^4.17.19", + "laravel-echo": "^1.15.1", + "laravel-vite-plugin": "^0.7.5", "postcss": "^8.4.18", + "pusher-js": "^8.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "tailwindcss": "^3.3.2", + "tailwindcss": "^3.2.1", "vite": "^4.0.0" } }, @@ -1893,6 +1895,15 @@ "node": ">=6" } }, + "node_modules/laravel-echo": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.15.1.tgz", + "integrity": "sha512-rW9XTXqs1v3sgcSFz7aE3/MPa2lfZjnsV/hrjyS/VYecQAx1lSP0hg3KumuR6ftkeneM093tVvTkRyKFumSyXg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/laravel-vite-plugin": { "version": "0.7.8", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz", @@ -1925,8 +1936,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.isequal": { "version": "4.5.0", @@ -2348,6 +2358,15 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, + "node_modules/pusher-js": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.2.0.tgz", + "integrity": "sha512-t3WhhR7vgAr5ARl0VNiAqXOb7g4hyW7CNA9Q11tnlcn8dX+1bFZhgRP6IqWVzTX9n7fgjcji3UQ3y8FEMc1o7Q==", + "dev": true, + "dependencies": { + "tweetnacl": "^1.0.3" + } + }, "node_modules/qs": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", @@ -2958,6 +2977,12 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "dev": true + }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", diff --git a/package.json b/package.json index 04f10c7..a65d5a6 100644 --- a/package.json +++ b/package.json @@ -11,20 +11,22 @@ "@vitejs/plugin-react": "^3.0.0", "autoprefixer": "^10.4.12", "axios": "^1.1.2", + "laravel-echo": "^1.15.1", "laravel-vite-plugin": "^0.7.5", "postcss": "^8.4.18", + "pusher-js": "^8.2.0", "react": "^18.2.0", "react-dom": "^18.2.0", "tailwindcss": "^3.2.1", "vite": "^4.0.0" }, "dependencies": { - "lodash": "^4.17.19", "@tinymce/tinymce-react": "^4.3.0", "chart.js": "^4.3.0", "flowbite": "^1.6.6", "flowbite-react": "^0.4.9", "is": "^3.3.0", + "lodash": "^4.17.19", "moment": "^2.29.4", "nprogress": "^0.2.0", "nuka-carousel": "^6.0.3", @@ -38,4 +40,4 @@ "react-use": "^17.4.0", "tinymce": "^6.4.2" } -} \ No newline at end of file +} diff --git a/resources/assets/notif.mp3 b/resources/assets/notif.mp3 new file mode 100644 index 0000000..1766d08 Binary files /dev/null and b/resources/assets/notif.mp3 differ diff --git a/resources/js/Layouts/AuthenticatedLayout.jsx b/resources/js/Layouts/AuthenticatedLayout.jsx index 8d5e9a4..06d8e7e 100644 --- a/resources/js/Layouts/AuthenticatedLayout.jsx +++ b/resources/js/Layouts/AuthenticatedLayout.jsx @@ -1,13 +1,15 @@ -import React, { useState, useEffect } from 'react' +import React, { useState, useEffect, useRef } from 'react' +import { router, Link, usePage } from '@inertiajs/react' +import { Breadcrumb, Flowbite } from 'flowbite-react' import { ToastContainer, toast } from 'react-toastify' +import { HiMenu, HiChevronDown, HiHome } from 'react-icons/hi' +import { HiOutlineBell } from 'react-icons/hi2' + import ApplicationLogo from '@/Components/Defaults/ApplicationLogo' import Dropdown from '@/Components/Defaults/Dropdown' -import { Link, usePage } from '@inertiajs/react' -import { Breadcrumb, Flowbite } from 'flowbite-react' -import { HiMenu, HiChevronDown, HiHome } from 'react-icons/hi' -import { router } from '@inertiajs/react' import SidebarNav from './Partials/SidebarNav' -import { HiOutlineBell } from 'react-icons/hi2' +import NotificationContent from './Partials/NotificationContent' +import NotificationDeposit from './Partials/NotificationDeposit' const customTheme = { button: { @@ -34,17 +36,18 @@ export default function Authenticated({ const [showingNavigationDropdown, setShowingNavigationDropdown] = useState(false) - const handleNotification = (notif) => { - fetch(route('api.notification.update', notif)) - router.get(route(route().current())) - } - useEffect(() => { if (flash.message !== null) { toast(flash.message.message, { type: flash.message.type }) } }, [flash]) + useEffect(() => { + if (typeof Notification !== undefined) { + Notification.requestPermission() + } + }, []) + return (
@@ -61,71 +64,10 @@ export default function Authenticated({
- - -
- -
-
- { - count_unread_notifications - } -
-
-
-
- - {notifications.map((notif) => ( -
- handleNotification( - notif - ) - } - key={notif.id} - > -
- {notif.description} -
-
- { - notif.format_created_at - } -
-
- ))} - {notifications.length > 0 && ( -
-
- router.get( - route( - 'notifications.index' - ) - ) - } - > - lihat semua -
-
- handleNotification() - } - > - tandai semua dibaca -
-
- )} -
-
+ +
+
+
diff --git a/resources/js/Layouts/Partials/NotificationContent.jsx b/resources/js/Layouts/Partials/NotificationContent.jsx new file mode 100644 index 0000000..5dee34a --- /dev/null +++ b/resources/js/Layouts/Partials/NotificationContent.jsx @@ -0,0 +1,67 @@ +import React from 'react' +import { router, usePage } from '@inertiajs/react' +import { HiOutlineBell } from 'react-icons/hi2' + +import Dropdown from '@/Components/Defaults/Dropdown' + +export default function NotificationContent() { + const { + props: { notifications, count_unread_notifications }, + } = usePage() + const handleNotification = (notif) => { + fetch(route('api.notification.update', notif)) + router.get(route(route().current())) + } + + return ( + + +
+ +
+
+ {count_unread_notifications} +
+
+
+
+ + {notifications.map((notif) => ( +
handleNotification(notif)} + key={notif.id} + > +
+ {notif.description} +
+
{notif.format_created_at}
+
+ ))} + {notifications.length > 0 && ( +
+
+ router.get(route('notifications.index')) + } + > + lihat semua +
+
handleNotification()} + > + tandai semua dibaca +
+
+ )} + {notifications.length === 0 && ( +
Tidak ada notifikasi
+ )} +
+
+ ) +} diff --git a/resources/js/Layouts/Partials/NotificationDeposit.jsx b/resources/js/Layouts/Partials/NotificationDeposit.jsx new file mode 100644 index 0000000..061f14f --- /dev/null +++ b/resources/js/Layouts/Partials/NotificationDeposit.jsx @@ -0,0 +1,132 @@ +import React, { useState, useEffect, useRef } from 'react' +import { router, usePage } from '@inertiajs/react' +import { HiOutlineCash } from 'react-icons/hi' + +import audioSrc from '@/../assets/notif.mp3' + +import Dropdown from '@/Components/Defaults/Dropdown' +import { browserNotification } from './helpers' +import { isEmpty } from 'lodash' + +export default function NotificationDeposit() { + const { + props: { deposit_notifications }, + } = usePage() + const [bounce, setBounce] = useState(false) + const [notif, setNotif] = useState(deposit_notifications) + const audioRef = useRef() + + const handleNotification = (notif) => { + if (isEmpty(notif) === false) { + fetch(route('api.notification.update', notif)) + router.get(notif.url) + return + } + fetch(route('api.notification.update')) + router.get(route('dashboard')) + } + + const handleAudioStop = () => { + if (isEmpty(audioRef.current) === false) { + audioRef.current.pause() + audioRef.current.currentTime = 0 + } + setBounce(false) + } + + const handleAudioPlay = () => { + if (audioRef.current.currentTime !== 0) { + handleAudioStop() + } + + try { + audioRef.current.play() + } catch (error) { + console.log(error) + } + + setBounce(true) + setTimeout(() => setBounce(false), 22000) + } + + const handleUpdate = async (e) => { + setNotif(e.message.deposit_notifications) + handleAudioPlay() + browserNotification( + 'Notifikasi Deposit', + e.message.description, + e.message.url + ) + } + + useEffect(() => { + window.Echo.channel('notification') + .listen('NotificationEvent', (e) => { + if (e.message.type === 'deposit') { + handleUpdate(e) + } + }) + .error((error) => { + console.error(error) + }) + return () => { + window.Echo.leave('notification') + } + }, []) + + return ( + + + +
handleAudioStop()} + > + +
+
+ {notif.length} +
+
+
+
+ + {notif.map((notif) => ( +
handleNotification(notif)} + key={notif.format_created_at} + > +
{notif.description}
+
{notif.format_created_at}
+
+ ))} + {notif.length > 0 && ( +
+
+ router.get(route('notifications.index')) + } + > + lihat semua +
+
handleNotification()} + > + tandai semua dibaca +
+
+ )} + {notif.length === 0 && ( +
Tidak ada notifikasi
+ )} +
+
+ ) +} diff --git a/resources/js/Layouts/Partials/helpers.js b/resources/js/Layouts/Partials/helpers.js index 7a6fa74..510b2a9 100644 --- a/resources/js/Layouts/Partials/helpers.js +++ b/resources/js/Layouts/Partials/helpers.js @@ -1,3 +1,5 @@ +import { router } from '@inertiajs/react' + export const filterOpenMenu = (user, item) => { const isAdmin = user.role === null if ('items' in item) { @@ -23,3 +25,19 @@ export const filterOpenMenu = (user, item) => { return item } } + +export const browserNotification = (title, message, url) => { + if (typeof Notification !== undefined) { + let permission = Notification.permission + if (permission === 'granted') { + let notification = new Notification(title, { + body: message, + }) + + notification.onclick = () => { + window.parent.focus() + router.visit(url) + } + } + } +} diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js index 366c49d..54935f7 100644 --- a/resources/js/bootstrap.js +++ b/resources/js/bootstrap.js @@ -1,5 +1,5 @@ -import _ from 'lodash'; -window._ = _; +import _ from 'lodash' +window._ = _ /** * We'll load the axios HTTP library which allows us to easily issue requests @@ -7,10 +7,10 @@ window._ = _; * CSRF token as a header based on the value of the "XSRF" token cookie. */ -import axios from 'axios'; -window.axios = axios; +import axios from 'axios' +window.axios = axios -window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; +window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest' /** * Echo exposes an expressive API for subscribing to channels and listening @@ -18,17 +18,19 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; * allows your team to easily build robust real-time web applications. */ -// import Echo from 'laravel-echo'; +import Echo from 'laravel-echo' +import Pusher from 'pusher-js' -// import Pusher from 'pusher-js'; -// window.Pusher = Pusher; - -// window.Echo = new Echo({ -// broadcaster: 'pusher', -// key: import.meta.env.VITE_PUSHER_APP_KEY, -// wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, -// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80, -// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443, -// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https', -// enabledTransports: ['ws', 'wss'], -// }); +window.Pusher = Pusher +window.Echo = new Echo({ + broadcaster: 'pusher', + key: import.meta.env.VITE_PUSHER_APP_KEY, + wsHost: import.meta.env.VITE_PUSHER_HOST, + wsPort: import.meta.env.VITE_PUSHER_PORT, + wssPort: import.meta.env.VITE_PUSHER_PORT, + cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER, + forceTLS: false, + encrypted: true, + disableStats: true, + enabledTransports: ['ws', 'wss'], +}) diff --git a/routes/api.php b/routes/api.php index e73cc4d..f3de73d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,5 +1,6 @@ id === (int) $id; }); + +// Broadcast::channel('deposit', function () { +// return true; +// });