Skip to content

Commit d0a81b8

Browse files
committed
Admin middleware
1 parent d66cb10 commit d0a81b8

File tree

12 files changed

+99
-25
lines changed

12 files changed

+99
-25
lines changed

README.md

196 Bytes

Usage

  1. After seeding, log in with the admin account: admin@test.com
  2. After seeding, log in with one of the seeded accounts:
    • Admin: admin@test.com / password
    • User: user@test.com / password
  3. Access the dashboard (toonly admin can manage users and send invitations)
  4. Recipients will receive invitation emails with registration links
  5. Users can register using the invitation tokens

Built With

  • Backend: Laravel 12, PHP 8.1+
  • Frontend: React 18, TypeScript, Inertia.js
  • Styling: Tailwind CSS, shadcn/ui components
  • Database: MySQL/SQLite
  • Build Tool: Vite

app/Http/Controllers/UserController.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ public function update(Request $request, string $id)
6262
/**
6363
* Remove the specified resource from storage.
6464
*/
65-
public function destroy(string $id)
65+
public function destroy(User $user)
6666
{
67-
//
67+
$user->delete();
68+
69+
return redirect()->route('users.index')->with('success', 'User deleted successfully');
6870
}
6971
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace App\Http\Middleware;
4+
5+
use Closure;
6+
use Illuminate\Http\Request;
7+
use Symfony\Component\HttpFoundation\Response;
8+
9+
class AdminMiddleware
10+
{
11+
/**
12+
* Handle an incoming request.
13+
*
14+
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
15+
*/
16+
public function handle(Request $request, Closure $next): Response
17+
{
18+
if ($request->user()->role !== 'admin') {
19+
abort(403, 'Unauthorized action.');
20+
}
21+
22+
return $next($request);
23+
}
24+
}

app/Http/Resources/UserResource.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public function toArray(Request $request): array
1717
return [
1818
'id' => $this->id,
1919
'avatar' => $this->avatar,
20+
'role' => $this->role,
2021
'name' => $this->name,
2122
'email' => $this->email,
2223
'email_verified_at' => $this->email_verified_at,

bootstrap/app.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use App\Http\Middleware\AdminMiddleware;
34
use App\Http\Middleware\HandleAppearance;
45
use App\Http\Middleware\HandleInertiaRequests;
56
use Illuminate\Foundation\Application;
@@ -9,8 +10,8 @@
910

1011
return Application::configure(basePath: dirname(__DIR__))
1112
->withRouting(
12-
web: __DIR__.'/../routes/web.php',
13-
commands: __DIR__.'/../routes/console.php',
13+
web: __DIR__ . '/../routes/web.php',
14+
commands: __DIR__ . '/../routes/console.php',
1415
health: '/up',
1516
)
1617
->withMiddleware(function (Middleware $middleware) {
@@ -21,6 +22,10 @@
2122
HandleInertiaRequests::class,
2223
AddLinkHeadersForPreloadedAssets::class,
2324
]);
25+
26+
$middleware->alias([
27+
'admin' => AdminMiddleware::class,
28+
]);
2429
})
2530
->withExceptions(function (Exceptions $exceptions) {
2631
//

database/seeders/UserSeeder.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ public function run(): void
1919
'email' => 'admin@test.com',
2020
'password' => Hash::make('password'),
2121
'email_verified_at' => now(),
22+
'role' => 'admin',
23+
]);
24+
25+
User::factory()->create([
26+
'name' => 'User',
27+
'email' => 'user@test.com',
28+
'password' => Hash::make('password'),
29+
'email_verified_at' => now(),
30+
'role' => 'user',
2231
]);
2332
}
2433
}

resources/js/components/app-sidebar.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { NavUser } from '@/components/nav-user';
44
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
55
import { type NavItem } from '@/types';
66
import { Link } from '@inertiajs/react';
7-
import { BookOpen, Folder, LayoutGrid, Mail, Users } from 'lucide-react';
7+
import { BookOpen, Github, LayoutGrid, Mail, Users } from 'lucide-react';
88
import AppLogo from './app-logo';
99

1010
const mainNavItems: NavItem[] = [
@@ -17,23 +17,25 @@ const mainNavItems: NavItem[] = [
1717
title: 'Users',
1818
href: '/users',
1919
icon: Users,
20+
requiresAdmin: true,
2021
},
2122
{
2223
title: 'Invitations',
2324
href: '/invitations',
2425
icon: Mail,
26+
requiresAdmin: true,
2527
},
2628
];
2729

2830
const footerNavItems: NavItem[] = [
2931
{
3032
title: 'Repository',
31-
href: 'https://github.com/laravel/react-starter-kit',
32-
icon: Folder,
33+
href: 'https://github.com/tarikulcodes/user-invitation',
34+
icon: Github,
3335
},
3436
{
3537
title: 'Documentation',
36-
href: 'https://laravel.com/docs/starter-kits#react',
38+
href: 'https://github.com/tarikulcodes/user-invitation/blob/main/README.md',
3739
icon: BookOpen,
3840
},
3941
];

resources/js/components/nav-main.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
11
import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
2-
import { type NavItem } from '@/types';
2+
import { User, type NavItem } from '@/types';
33
import { Link, usePage } from '@inertiajs/react';
44

55
export function NavMain({ items = [] }: { items: NavItem[] }) {
66
const page = usePage();
7+
const { user } = page.props.auth as { user: User };
78
return (
89
<SidebarGroup className="px-2 py-0">
910
<SidebarGroupLabel>Platform</SidebarGroupLabel>
1011
<SidebarMenu>
11-
{items.map((item) => (
12-
<SidebarMenuItem key={item.title}>
13-
<SidebarMenuButton asChild isActive={page.url.startsWith(item.href)} tooltip={{ children: item.title }}>
14-
<Link href={item.href} prefetch>
15-
{item.icon && <item.icon />}
16-
<span>{item.title}</span>
17-
</Link>
18-
</SidebarMenuButton>
19-
</SidebarMenuItem>
20-
))}
12+
{items.map((item) => {
13+
if (item.requiresAdmin && user.role !== 'admin') {
14+
return null;
15+
}
16+
return (
17+
<SidebarMenuItem key={item.title}>
18+
<SidebarMenuButton asChild isActive={page.url.startsWith(item.href)} tooltip={{ children: item.title }}>
19+
<Link href={item.href} prefetch>
20+
{item.icon && <item.icon />}
21+
<span>{item.title}</span>
22+
</Link>
23+
</SidebarMenuButton>
24+
</SidebarMenuItem>
25+
);
26+
})}
2127
</SidebarMenu>
2228
</SidebarGroup>
2329
);

resources/js/pages/users/index.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { DataTable } from '@/components/datatable';
22
import { DataTableColumnHeader } from '@/components/datatable-column-header';
33
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
44
import { Badge } from '@/components/ui/badge';
5+
import { Button } from '@/components/ui/button';
56
import { Card, CardContent } from '@/components/ui/card';
67
import AppLayout from '@/layouts/app-layout';
78
import { User } from '@/types';
8-
import { Head, Link } from '@inertiajs/react';
9+
import { Head, Link, router } from '@inertiajs/react';
910
import { ColumnDef } from '@tanstack/react-table';
1011

1112
const UsersIndex = ({ users }: { users: User[] }) => {
@@ -48,7 +49,7 @@ const UsersIndex = ({ users }: { users: User[] }) => {
4849
header: 'Role',
4950
accessorKey: 'role',
5051
cell: ({ row }) => {
51-
return <Badge variant="outline">{row.original.role?.charAt(0).toUpperCase() + row.original.role?.slice(1)}</Badge>;
52+
return <Badge variant="outline">{row.original.role}</Badge>;
5253
},
5354
},
5455
{
@@ -59,11 +60,27 @@ const UsersIndex = ({ users }: { users: User[] }) => {
5960
header: 'Updated At',
6061
accessorKey: 'updated_at',
6162
},
63+
{
64+
header: 'Actions',
65+
cell: ({ row }) => {
66+
return (
67+
<Button
68+
variant="destructive"
69+
size="sm"
70+
onClick={() => router.delete(route('users.destroy', row.original.id))}
71+
disabled={row.original.id === 1 || row.original.id === 2}
72+
>
73+
Delete
74+
</Button>
75+
);
76+
},
77+
},
6278
];
6379

6480
return (
6581
<AppLayout>
6682
<Head title="Users" />
83+
{/* <pre>{JSON.stringify(users, null, 2)}</pre> */}
6784
<div className="p-4">
6885
<h1 className="mb-4 text-2xl font-bold">Users</h1>
6986
<Card>

resources/js/types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface NavItem {
2020
href: string;
2121
icon?: LucideIcon | null;
2222
isActive?: boolean;
23+
requiresAdmin?: boolean;
2324
}
2425

2526
export interface SharedData {

0 commit comments

Comments
 (0)