fix notification

dev
Aji Kamaludin 1 year ago
parent 286e0636b6
commit 401a242822
No known key found for this signature in database
GPG Key ID: 19058F67F0083AD3

@ -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

@ -0,0 +1,34 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class NotificationEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(public string|array $message)
{
info(self::class, ['message' => $message, 'info' => 'triggered']);
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn()
{
return new Channel('notification');
}
}

@ -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(),
]);
}
}

@ -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
{
@ -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,
'url' => route('deposit.edit', $this),
'type' => Notification::TYPE_DEPOSIT,
]);
}
@ -174,18 +179,36 @@ 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,
'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,
'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()

@ -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 = [

@ -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"

199
composer.lock generated

@ -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",

@ -39,7 +39,7 @@ return [
'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' => [

@ -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();

35
package-lock.json generated

@ -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",

@ -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",

Binary file not shown.

@ -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 (
<Flowbite theme={{ theme: customTheme }}>
<div className="min-h-screen flex flex-col bg-gray-100 dark:bg-gray-700">
@ -61,71 +64,10 @@ export default function Authenticated({
<div className="hidden sm:flex sm:items-center sm:ml-6">
<div className="ml-3 relative">
<Dropdown>
<Dropdown.Trigger>
<div className="flex flex-row">
<HiOutlineBell className="h-6 w-6" />
<div>
<div className="bg-blue-300 text-blue-600 rounded-lg px-1 text-xs -ml-2">
{
count_unread_notifications
}
<NotificationDeposit />
</div>
</div>
</div>
</Dropdown.Trigger>
<Dropdown.Content width="64">
{notifications.map((notif) => (
<div
className={`px-4 py-2 hover:bg-gray-100 border-b`}
onClick={() =>
handleNotification(
notif
)
}
key={notif.id}
>
<div
className={`${
+notif.is_read ===
0 && 'font-bold'
}`}
>
{notif.description}
</div>
<div className="text-xs">
{
notif.format_created_at
}
</div>
</div>
))}
{notifications.length > 0 && (
<div className="w-full flex flex-row justify-between px-3">
<div
className="text-xs hover:text-blue-500 hover:underline"
onClick={() =>
router.get(
route(
'notifications.index'
)
)
}
>
lihat semua
</div>
<div
className="text-xs hover:text-blue-500 hover:underline"
onClick={() =>
handleNotification()
}
>
tandai semua dibaca
</div>
</div>
)}
</Dropdown.Content>
</Dropdown>
<div className="ml-3 relative">
<NotificationContent />
</div>
<div className="ml-3 relative">
<Dropdown>

@ -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 (
<Dropdown>
<Dropdown.Trigger>
<div className="flex flex-row">
<HiOutlineBell className="h-6 w-6" />
<div>
<div className="bg-blue-300 text-blue-600 rounded-lg px-1 text-xs -ml-2">
{count_unread_notifications}
</div>
</div>
</div>
</Dropdown.Trigger>
<Dropdown.Content width="64">
{notifications.map((notif) => (
<div
className={`px-4 py-2 hover:bg-gray-100 border-b`}
onClick={() => handleNotification(notif)}
key={notif.id}
>
<div
className={`${+notif.is_read === 0 && 'font-bold'}`}
>
{notif.description}
</div>
<div className="text-xs">{notif.format_created_at}</div>
</div>
))}
{notifications.length > 0 && (
<div className="w-full flex flex-row justify-between px-3">
<div
className="text-xs hover:text-blue-500 hover:underline"
onClick={() =>
router.get(route('notifications.index'))
}
>
lihat semua
</div>
<div
className="text-xs hover:text-blue-500 hover:underline"
onClick={() => handleNotification()}
>
tandai semua dibaca
</div>
</div>
)}
{notifications.length === 0 && (
<div className="px-4 py-2 ">Tidak ada notifikasi</div>
)}
</Dropdown.Content>
</Dropdown>
)
}

@ -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 (
<Dropdown>
<audio id="audio" loop={false} autoPlay={false} ref={audioRef}>
<source src={audioSrc} type="audio/mpeg" />
</audio>
<Dropdown.Trigger>
<div
className={`flex flex-row ${
bounce && 'motion-safe:animate-bounce'
}`}
onClick={() => handleAudioStop()}
>
<HiOutlineCash className="h-6 w-6" />
<div>
<div className="bg-red-300 text-red-600 rounded-lg px-1 text-xs -ml-2">
{notif.length}
</div>
</div>
</div>
</Dropdown.Trigger>
<Dropdown.Content width="64">
{notif.map((notif) => (
<div
className={`px-4 py-2 hover:bg-gray-100 border-b`}
onClick={() => handleNotification(notif)}
key={notif.format_created_at}
>
<div className="font-bold">{notif.description}</div>
<div className="text-xs">{notif.format_created_at}</div>
</div>
))}
{notif.length > 0 && (
<div className="w-full flex flex-row justify-between px-3">
<div
className="text-xs hover:text-blue-500 hover:underline"
onClick={() =>
router.get(route('notifications.index'))
}
>
lihat semua
</div>
<div
className="text-xs hover:text-blue-500 hover:underline"
onClick={() => handleNotification()}
>
tandai semua dibaca
</div>
</div>
)}
{notif.length === 0 && (
<div className="px-4 py-2 ">Tidak ada notifikasi</div>
)}
</Dropdown.Content>
</Dropdown>
)
}

@ -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)
}
}
}
}

@ -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'],
})

@ -1,5 +1,6 @@
<?php
use App\Events\NotificationEvent;
use App\Http\Controllers\Api\CustomerController;
use App\Http\Controllers\Api\CustomerLevelController;
use App\Http\Controllers\Api\LocationController;

@ -16,3 +16,7 @@ use Illuminate\Support\Facades\Broadcast;
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
// Broadcast::channel('deposit', function () {
// return true;
// });

Loading…
Cancel
Save