added form login, user for frontend and backend

This commit is contained in:
Alexander Gabriel 2026-06-28 16:01:16 +00:00
parent a5ef904c1e
commit 5488a80b91
14 changed files with 350 additions and 15 deletions

View File

@ -0,0 +1,11 @@
<?php
namespace App\Filament\Resources\Users\Pages;
use App\Filament\Resources\Users\UserResource;
use Filament\Resources\Pages\CreateRecord;
class CreateUser extends CreateRecord
{
protected static string $resource = UserResource::class;
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Users\Pages;
use App\Filament\Resources\Users\UserResource;
use Filament\Actions\DeleteAction;
use Filament\Resources\Pages\EditRecord;
class EditUser extends EditRecord
{
protected static string $resource = UserResource::class;
protected function getHeaderActions(): array
{
return [
DeleteAction::make(),
];
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\Users\Pages;
use App\Filament\Resources\Users\UserResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListUsers extends ListRecords
{
protected static string $resource = UserResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make(),
];
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Filament\Resources\Users\Schemas;
use Filament\Forms\Components\Checkbox;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Schema;
class UserForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
TextInput::make('name')
->label('Name')
->required(),
TextInput::make('email')
->label('E-Mail')
->email()
->required()
->unique(ignoreRecord: true),
TextInput::make('password')
->label('Passwort')
->password()
->revealable()
->required(fn (string $operation) => $operation === 'create')
->dehydrateStateUsing(fn ($state) => filled($state) ? bcrypt($state) : null)
->dehydrated(fn ($state) => filled($state))
->minLength(8),
Checkbox::make('is_admin')
->label('Administrator'),
]);
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Filament\Resources\Users\Tables;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
class UsersTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('name')
->label('Name')
->searchable()
->sortable(),
TextColumn::make('email')
->label('E-Mail')
->searchable()
->sortable(),
IconColumn::make('is_admin')
->label('Administrator'),
TextColumn::make('created_at')
->label('Erstellt')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([])
->recordActions([
EditAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
DeleteBulkAction::make(),
]),
]);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Filament\Resources\Users;
use App\Filament\Resources\Users\Pages\CreateUser;
use App\Filament\Resources\Users\Pages\EditUser;
use App\Filament\Resources\Users\Pages\ListUsers;
use App\Filament\Resources\Users\Schemas\UserForm;
use App\Filament\Resources\Users\Tables\UsersTable;
use App\Models\User;
use BackedEnum;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
class UserResource extends Resource
{
protected static ?string $model = User::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedUsers;
protected static ?string $recordTitleAttribute = 'name';
protected static ?string $navigationLabel = 'Benutzer';
protected static ?string $modelLabel = 'Benutzer';
protected static ?string $pluralModelLabel = 'Benutzer';
public static function form(Schema $schema): Schema
{
return UserForm::configure($schema);
}
public static function table(Table $table): Table
{
return UsersTable::configure($table);
}
public static function getRelations(): array
{
return [];
}
public static function getPages(): array
{
return [
'index' => ListUsers::route('/'),
'create' => CreateUser::route('/create'),
'edit' => EditUser::route('/{record}/edit'),
];
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
public function show()
{
return view('auth.login');
}
public function login(Request $request)
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials, $request->boolean('remember'))) {
$request->session()->regenerate();
return redirect()->intended('/');
}
return back()->withErrors(['email' => 'Die eingegebenen Daten stimmen nicht überein.'])->onlyInput('email');
}
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/login');
}
}

View File

@ -2,31 +2,33 @@
namespace App\Models; namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory; use Database\Factories\UserFactory;
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Database\Eloquent\Attributes\Fillable; use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
#[Fillable(['name', 'email', 'password'])] #[Fillable(['name', 'email', 'password', 'is_admin'])]
#[Hidden(['password', 'remember_token'])] #[Hidden(['password', 'remember_token'])]
class User extends Authenticatable class User extends Authenticatable implements FilamentUser
{ {
/** @use HasFactory<UserFactory> */ /** @use HasFactory<UserFactory> */
use HasFactory, Notifiable; use HasFactory, Notifiable;
/** public function canAccessPanel(Panel $panel): bool
* Get the attributes that should be cast. {
* return $this->is_admin;
* @return array<string, string> }
*/
protected function casts(): array protected function casts(): array
{ {
return [ return [
'email_verified_at' => 'datetime', 'email_verified_at' => 'datetime',
'password' => 'hashed', 'password' => 'hashed',
'is_admin' => 'boolean',
]; ];
} }
} }

View File

@ -30,6 +30,7 @@ class UserFactory extends Factory
'email_verified_at' => now(), 'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'), 'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10), 'remember_token' => Str::random(10),
'is_admin' => false,
]; ];
} }

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_admin')->default(false)->after('remember_token');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('is_admin');
});
}
};

View File

@ -12,6 +12,7 @@ class DatabaseSeeder extends Seeder
public function run(): void public function run(): void
{ {
$this->call([ $this->call([
UserSeeder::class,
PhotoprofileSeeder::class, PhotoprofileSeeder::class,
]); ]);
} }

View File

@ -0,0 +1,31 @@
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class UserSeeder extends Seeder
{
public function run(): void
{
User::firstOrCreate(
['email' => 'admin@fotobox.local'],
[
'name' => 'Admin',
'password' => Hash::make('admin'),
'is_admin' => true,
]
);
User::firstOrCreate(
['email' => 'user@fotobox.local'],
[
'name' => 'User',
'password' => Hash::make('user'),
'is_admin' => false,
]
);
}
}

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fotobox Anmelden</title>
@if (file_exists(public_path('build/manifest.json')) || file_exists(public_path('hot')))
@vite(['resources/css/app.css', 'resources/js/app.js'])
@endif
</head>
<body class="bg-gray-950 text-white min-h-screen flex flex-col items-center justify-center p-6">
<h1 class="text-3xl font-bold mb-8 tracking-wide">Fotobox</h1>
<form method="POST" action="{{ route('login') }}" class="w-full max-w-sm flex flex-col gap-4">
@csrf
<div class="flex flex-col gap-1">
<label for="email" class="text-sm text-gray-400">E-Mail</label>
<input id="email" type="email" name="email" value="{{ old('email') }}" required autofocus
class="rounded-xl bg-white/10 border border-white/20 px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-white/40">
@error('email')
<span class="text-sm text-red-400">{{ $message }}</span>
@enderror
</div>
<div class="flex flex-col gap-1">
<label for="password" class="text-sm text-gray-400">Passwort</label>
<input id="password" type="password" name="password" required
class="rounded-xl bg-white/10 border border-white/20 px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-white/40">
</div>
<label class="flex items-center gap-2 text-sm text-gray-400 cursor-pointer">
<input type="checkbox" name="remember" class="rounded">
Angemeldet bleiben
</label>
<button type="submit"
class="mt-2 px-10 py-4 bg-white text-gray-950 font-semibold text-lg rounded-full shadow-lg hover:bg-gray-200 active:scale-95 transition-all duration-150">
Anmelden
</button>
</form>
</body>
</html>

View File

@ -1,8 +1,16 @@
<?php <?php
use App\Http\Controllers\LoginController;
use App\Http\Controllers\PhotoController; use App\Http\Controllers\PhotoController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
Route::middleware('auth.basic')->group(function () { Route::middleware('guest')->group(function () {
Route::get('/login', [LoginController::class, 'show'])->name('login');
Route::post('/login', [LoginController::class, 'login']);
});
Route::post('/logout', [LoginController::class, 'logout'])->name('logout')->middleware('auth');
Route::middleware('auth')->group(function () {
Route::get('/', [PhotoController::class, 'index'])->name('camera'); Route::get('/', [PhotoController::class, 'index'])->name('camera');
}); });