Compare commits
No commits in common. "4577d45e62ecf094204a5be2a819cc5ae7afd9f4" and "e615547317ba44480d6e998e6a2b3f56960d012d" have entirely different histories.
4577d45e62
...
e615547317
@ -1,47 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
|
||||||
|
|
||||||
use App\Models\Notification;
|
|
||||||
use App\Services\NotificationService;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class NotificationController extends Controller
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly NotificationService $service,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function markAllRead(Request $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->service->markAllAsRead($request->user());
|
|
||||||
|
|
||||||
return back();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function destroyAll(Request $request): RedirectResponse
|
|
||||||
{
|
|
||||||
$this->service->deleteAll($request->user());
|
|
||||||
|
|
||||||
return back();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function markRead(Request $request, Notification $notification): RedirectResponse
|
|
||||||
{
|
|
||||||
abort_unless($notification->user_id === $request->user()->id, 403);
|
|
||||||
|
|
||||||
$this->service->markAsRead($notification);
|
|
||||||
|
|
||||||
return back();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function destroy(Request $request, Notification $notification): RedirectResponse
|
|
||||||
{
|
|
||||||
abort_unless($notification->user_id === $request->user()->id, 403);
|
|
||||||
|
|
||||||
$this->service->delete($notification);
|
|
||||||
|
|
||||||
return back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use App\Services\NotificationService;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Inertia\Inertia;
|
|
||||||
use Inertia\Middleware;
|
use Inertia\Middleware;
|
||||||
|
|
||||||
class HandleInertiaRequests extends Middleware
|
class HandleInertiaRequests extends Middleware
|
||||||
@ -44,12 +42,6 @@ public function share(Request $request): array
|
|||||||
'user' => $request->user(),
|
'user' => $request->user(),
|
||||||
],
|
],
|
||||||
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
||||||
'unreadNotificationsCount' => fn () => $request->user()
|
|
||||||
? app(NotificationService::class)->unreadCount($request->user())
|
|
||||||
: 0,
|
|
||||||
'notifications' => Inertia::merge(fn () => $request->user()
|
|
||||||
? app(NotificationService::class)->paginated($request->user())
|
|
||||||
: null)->append('data', 'id'),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
|
|
||||||
#[Guarded(['id'])]
|
|
||||||
class Notification extends Model
|
|
||||||
{
|
|
||||||
use HasFactory;
|
|
||||||
|
|
||||||
protected function casts(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'is_read' => 'boolean',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function user(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -7,7 +7,6 @@
|
|||||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
@ -50,9 +49,4 @@ public function lecturer(): HasOne
|
|||||||
{
|
{
|
||||||
return $this->hasOne(Lecturer::class);
|
return $this->hasOne(Lecturer::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function notifications(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(Notification::class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
|
|
||||||
use App\Models\Notification;
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
|
||||||
|
|
||||||
class NotificationService
|
|
||||||
{
|
|
||||||
public function paginated(User $user, int $perPage = 15): LengthAwarePaginator
|
|
||||||
{
|
|
||||||
return Notification::query()
|
|
||||||
->where('user_id', $user->id)
|
|
||||||
->orderByDesc('created_at')
|
|
||||||
->orderByDesc('id')
|
|
||||||
->paginate($perPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function unreadCount(User $user): int
|
|
||||||
{
|
|
||||||
return Notification::query()
|
|
||||||
->where('user_id', $user->id)
|
|
||||||
->where('is_read', false)
|
|
||||||
->count();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function markAsRead(Notification $notification): void
|
|
||||||
{
|
|
||||||
$notification->update(['is_read' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function markAllAsRead(User $user): void
|
|
||||||
{
|
|
||||||
Notification::query()
|
|
||||||
->where('user_id', $user->id)
|
|
||||||
->where('is_read', false)
|
|
||||||
->update(['is_read' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(Notification $notification): bool
|
|
||||||
{
|
|
||||||
return $notification->delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteAll(User $user): void
|
|
||||||
{
|
|
||||||
Notification::query()->where('user_id', $user->id)->delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::create('notifications', function (Blueprint $table) {
|
|
||||||
$table->id();
|
|
||||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
|
||||||
$table->string('title', 150)->nullable();
|
|
||||||
$table->text('content')->nullable();
|
|
||||||
$table->boolean('is_read')->nullable()->default(false);
|
|
||||||
$table->timestamps();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('notifications');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -30,7 +30,6 @@ public function run(): void
|
|||||||
TuitionPaymentSeeder::class,
|
TuitionPaymentSeeder::class,
|
||||||
AnnouncementSeeder::class,
|
AnnouncementSeeder::class,
|
||||||
LetterRequestSeeder::class,
|
LetterRequestSeeder::class,
|
||||||
NotificationSeeder::class,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Database\Seeders;
|
|
||||||
|
|
||||||
use App\Models\Notification;
|
|
||||||
use Illuminate\Database\Seeder;
|
|
||||||
|
|
||||||
class NotificationSeeder extends Seeder
|
|
||||||
{
|
|
||||||
public function run(): void
|
|
||||||
{
|
|
||||||
Notification::insert([
|
|
||||||
[
|
|
||||||
'user_id' => 1,
|
|
||||||
'title' => 'Tugas baru diunggah',
|
|
||||||
'content' => 'Tugas "Membuat Landing Page" telah tersedia di kelas SI-5A',
|
|
||||||
'is_read' => false,
|
|
||||||
'created_at' => '2026-08-05 09:05:00',
|
|
||||||
'updated_at' => '2026-08-05 09:05:00',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'user_id' => 2,
|
|
||||||
'title' => 'Pembayaran UKT diterima sebagian',
|
|
||||||
'content' => 'Pembayaran cicilan tahap 1 sebesar Rp1.500.000 telah tercatat',
|
|
||||||
'is_read' => true,
|
|
||||||
'created_at' => '2026-08-04 11:05:00',
|
|
||||||
'updated_at' => '2026-08-04 12:00:00',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { Breadcrumbs } from '@/components/breadcrumbs';
|
import { Breadcrumbs } from '@/components/breadcrumbs';
|
||||||
import { NavUser } from '@/components/nav-user';
|
import { NavUser } from '@/components/nav-user';
|
||||||
import { NotificationBell } from '@/components/notification-bell';
|
|
||||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||||
import type { BreadcrumbItem as BreadcrumbItemType } from '@/types';
|
import type { BreadcrumbItem as BreadcrumbItemType } from '@/types';
|
||||||
|
|
||||||
@ -15,8 +14,7 @@ export function AppSidebarHeader({
|
|||||||
<SidebarTrigger className="-ml-1" />
|
<SidebarTrigger className="-ml-1" />
|
||||||
<Breadcrumbs breadcrumbs={breadcrumbs} />
|
<Breadcrumbs breadcrumbs={breadcrumbs} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-auto flex items-center gap-2">
|
<div className="ml-auto">
|
||||||
<NotificationBell />
|
|
||||||
<NavUser />
|
<NavUser />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@ -1,207 +0,0 @@
|
|||||||
import { Combobox as ComboboxPrimitive } from '@base-ui/react';
|
|
||||||
import { router, usePage, WhenVisible } from '@inertiajs/react';
|
|
||||||
import { format } from 'date-fns';
|
|
||||||
import { Bell, CheckCheck, Loader2, Trash2 } from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { ConfirmDialog } from '@/components/confirm-dialog';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import { Combobox, ComboboxContent } from '@/components/ui/combobox';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
import { destroy, markAllRead, clearAll, read } from '@/routes/notifications';
|
|
||||||
import type { Notification, NotificationPage } from '@/types/notification';
|
|
||||||
|
|
||||||
export function NotificationBell() {
|
|
||||||
const { props } = usePage<{ notifications?: NotificationPage }>();
|
|
||||||
const unreadCount = props.unreadNotificationsCount ?? 0;
|
|
||||||
const notifications = props.notifications ?? {
|
|
||||||
data: [],
|
|
||||||
current_page: 1,
|
|
||||||
last_page: 1,
|
|
||||||
per_page: 15,
|
|
||||||
total: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [clearAllOpen, setClearAllOpen] = useState(false);
|
|
||||||
|
|
||||||
const hasMore = notifications.current_page < notifications.last_page;
|
|
||||||
const hasNotifications = notifications.data.length > 0;
|
|
||||||
const hasUnread = notifications.data.some((n) => !n.is_read);
|
|
||||||
|
|
||||||
function handleMarkAllRead() {
|
|
||||||
router.patch(markAllRead().url, {}, { preserveScroll: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClearAll() {
|
|
||||||
router.delete(clearAll().url, {
|
|
||||||
preserveScroll: true,
|
|
||||||
onSuccess: () => setClearAllOpen(false),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMarkRead(notification: Notification) {
|
|
||||||
router.patch(read(notification.id).url, {}, { preserveScroll: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDelete(notification: Notification) {
|
|
||||||
router.delete(destroy(notification.id).url, { preserveScroll: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Combobox open={open} onOpenChange={setOpen}>
|
|
||||||
<ComboboxPrimitive.Trigger
|
|
||||||
render={
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="relative"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Bell className="h-5 w-5" />
|
|
||||||
{unreadCount > 0 && (
|
|
||||||
<span className="absolute top-1 right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-destructive px-1 text-[10px] font-medium text-white">
|
|
||||||
{unreadCount > 99 ? '99+' : unreadCount}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
<span className="sr-only">Notifikasi</span>
|
|
||||||
</ComboboxPrimitive.Trigger>
|
|
||||||
|
|
||||||
<ComboboxContent
|
|
||||||
align="end"
|
|
||||||
className="w-96 max-w-[90vw] min-w-96 p-0"
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between border-b p-3">
|
|
||||||
<span className="font-medium">Notifikasi</span>
|
|
||||||
<div className="flex gap-1">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
title="Tandai semua dibaca"
|
|
||||||
disabled={!hasUnread}
|
|
||||||
onClick={handleMarkAllRead}
|
|
||||||
>
|
|
||||||
<CheckCheck className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
title="Hapus semua"
|
|
||||||
disabled={!hasNotifications}
|
|
||||||
onClick={() => setClearAllOpen(true)}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="max-h-96 overflow-y-auto">
|
|
||||||
{hasNotifications ? (
|
|
||||||
<div className="flex flex-col divide-y">
|
|
||||||
{notifications.data.map((notification) => (
|
|
||||||
<div
|
|
||||||
key={notification.id}
|
|
||||||
className={cn(
|
|
||||||
'flex items-start justify-between gap-2 p-3',
|
|
||||||
!notification.is_read &&
|
|
||||||
'bg-primary/5',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-start gap-2">
|
|
||||||
{!notification.is_read && (
|
|
||||||
<span className="mt-1.5 h-2 w-2 shrink-0 rounded-full bg-primary" />
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium">
|
|
||||||
{notification.title ?? '-'}
|
|
||||||
</p>
|
|
||||||
{notification.content && (
|
|
||||||
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
||||||
{notification.content}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<p className="mt-1 text-xs text-muted-foreground">
|
|
||||||
{format(
|
|
||||||
new Date(
|
|
||||||
notification.created_at,
|
|
||||||
),
|
|
||||||
'd MMM yyyy, HH:mm',
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex shrink-0 items-center gap-1">
|
|
||||||
{!notification.is_read && (
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-7 w-7"
|
|
||||||
title="Tandai dibaca"
|
|
||||||
onClick={() =>
|
|
||||||
handleMarkRead(
|
|
||||||
notification,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<CheckCheck className="h-3.5 w-3.5" />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-7 w-7"
|
|
||||||
title="Hapus"
|
|
||||||
onClick={() =>
|
|
||||||
handleDelete(notification)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-3.5 w-3.5 text-destructive" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{hasMore && (
|
|
||||||
<WhenVisible
|
|
||||||
always
|
|
||||||
data="notifications"
|
|
||||||
params={{
|
|
||||||
data: {
|
|
||||||
page:
|
|
||||||
notifications.current_page +
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
fallback={
|
|
||||||
<div className="flex justify-center py-3">
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="flex justify-center py-3">
|
|
||||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
|
||||||
</div>
|
|
||||||
</WhenVisible>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex h-24 items-center justify-center text-sm text-muted-foreground">
|
|
||||||
Belum ada notifikasi.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ComboboxContent>
|
|
||||||
</Combobox>
|
|
||||||
|
|
||||||
<ConfirmDialog
|
|
||||||
open={clearAllOpen}
|
|
||||||
onOpenChange={setClearAllOpen}
|
|
||||||
title="Hapus Semua Notifikasi"
|
|
||||||
description="Apakah Anda yakin ingin menghapus semua notifikasi? Tindakan ini tidak dapat dibatalkan."
|
|
||||||
confirmLabel="Hapus"
|
|
||||||
onConfirm={handleClearAll}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -8,6 +8,7 @@ function getInitial(name: string): string {
|
|||||||
|
|
||||||
export function useInitials(): GetInitialsFn {
|
export function useInitials(): GetInitialsFn {
|
||||||
return useCallback((fullName: string): string => {
|
return useCallback((fullName: string): string => {
|
||||||
|
console.log(fullName)
|
||||||
const names = fullName.trim().split(/\s+/u).filter(Boolean);
|
const names = fullName.trim().split(/\s+/u).filter(Boolean);
|
||||||
|
|
||||||
if (names.length === 0) {
|
if (names.length === 0) {
|
||||||
|
|||||||
1
resources/js/types/global.d.ts
vendored
1
resources/js/types/global.d.ts
vendored
@ -13,7 +13,6 @@ declare module '@inertiajs/core' {
|
|||||||
name: string;
|
name: string;
|
||||||
auth: Auth;
|
auth: Auth;
|
||||||
sidebarOpen: boolean;
|
sidebarOpen: boolean;
|
||||||
unreadNotificationsCount: number;
|
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
export type Notification = {
|
|
||||||
id: number;
|
|
||||||
title: string | null;
|
|
||||||
content: string | null;
|
|
||||||
is_read: boolean;
|
|
||||||
created_at: string;
|
|
||||||
updated_at: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NotificationPage = {
|
|
||||||
data: Notification[];
|
|
||||||
current_page: number;
|
|
||||||
last_page: number;
|
|
||||||
per_page: number;
|
|
||||||
total: number;
|
|
||||||
};
|
|
||||||
@ -1,19 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\NotificationController;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::inertia('/', 'welcome')->name('home');
|
Route::inertia('/', 'welcome')->name('home');
|
||||||
|
|
||||||
Route::middleware(['auth', 'verified'])->group(function () {
|
Route::middleware(['auth', 'verified'])->group(function () {
|
||||||
Route::inertia('dashboard', 'dashboard')->name('dashboard');
|
Route::inertia('dashboard', 'dashboard')->name('dashboard');
|
||||||
|
|
||||||
Route::prefix('notifications')->name('notifications.')->group(function () {
|
|
||||||
Route::patch('mark-all-read', [NotificationController::class, 'markAllRead'])->name('mark-all-read');
|
|
||||||
Route::delete('clear-all', [NotificationController::class, 'destroyAll'])->name('clear-all');
|
|
||||||
Route::patch('{notification}/read', [NotificationController::class, 'markRead'])->name('read');
|
|
||||||
Route::delete('{notification}', [NotificationController::class, 'destroy'])->name('destroy');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
require __DIR__.'/settings.php';
|
require __DIR__.'/settings.php';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user