diff --git a/app/Http/Controllers/Admin/AuthController.php b/app/Http/Controllers/Admin/AuthController.php new file mode 100644 index 00000000..3f732c73 --- /dev/null +++ b/app/Http/Controllers/Admin/AuthController.php @@ -0,0 +1,60 @@ +validate([ + 'username' => 'required|string', + 'password' => 'required|string', + ]); + + $admin = AdminUser::where('username', $request->username)->first(); + + if (!$admin || !Hash::check($request->password, $admin->password)) { + return back()->withErrors([ + 'username' => 'The provided credentials do not match our records.', + ])->onlyInput('username'); + } + + // Store admin ID in session + Session::put('admin_id', $admin->id); + Session::put('admin_username', $admin->username); + + return redirect()->route('admin.dashboard'); + } + + /** + * Handle admin logout. + */ + public function logout(Request $request): RedirectResponse + { + Session::forget('admin_id'); + Session::forget('admin_username'); + Session::flush(); + + return redirect()->route('admin.login'); + } +} diff --git a/app/Http/Controllers/Admin/DashboardController.php b/app/Http/Controllers/Admin/DashboardController.php new file mode 100644 index 00000000..ceadaad7 --- /dev/null +++ b/app/Http/Controllers/Admin/DashboardController.php @@ -0,0 +1,11 @@ +route('admin.login'); + } + + return $next($request); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 5c02a599..d0e01a00 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -16,7 +16,10 @@ return Application::configure(basePath: dirname(__DIR__)) \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, ]); - // + // Register admin auth middleware + $middleware->alias([ + 'admin.auth' => \App\Http\Middleware\AdminAuth::class, + ]); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/cline_docs/activeContext.md b/cline_docs/activeContext.md index 859d4373..ce429b39 100644 --- a/cline_docs/activeContext.md +++ b/cline_docs/activeContext.md @@ -1,11 +1,11 @@ # Active Context - Current Work Status -## Current Task: v2.0 LARAVEL + INERTIA + VUE FOUNDATION COMPLETE ✅ -**MAD Lawsuit Website v2.0 - Complete Technology Stack Migration**: +## Current Task: v2.0 PHASE 2 ADMIN DASHBOARD - IN PROGRESS 🚧 +**MAD Lawsuit Website v2.0 - Admin Dashboard Development**: - **v1.0 Status**: Fully operational at https://mad-lawsuit.org (Next.js + Express + React) - **v2.0 Goal**: Rebuild with Laravel + Inertia + Vue + TypeScript -- **Current Phase**: Foundation complete, home page working, ready for features -- **Status**: TESTED LOCALLY - WEBSITE RENDERING CORRECTLY ✅ +- **Current Phase**: Phase 2 - Admin Dashboard (50% complete) +- **Status**: AUTHENTICATION FOUNDATION COMPLETE, NEED CONTROLLERS & VIEWS ⏳ ### v2.0 Technology Stack **Backend**: diff --git a/cline_docs/phase2_progress.md b/cline_docs/phase2_progress.md new file mode 100644 index 00000000..16db55ee --- /dev/null +++ b/cline_docs/phase2_progress.md @@ -0,0 +1,204 @@ +# Phase 2: Admin Dashboard Progress + +## Status: 50% COMPLETE - Authentication Foundation Done + +### ✅ Completed Components + +#### 1. Admin Authentication System +**Files Created:** +- `app/Http/Controllers/Admin/AuthController.php` - Login/logout logic +- `app/Http/Middleware/AdminAuth.php` - Session-based auth middleware +- `bootstrap/app.php` - Middleware registered as 'admin.auth' + +**AuthController Methods:** +- `showLogin()` - Renders Admin/Login Inertia page +- `login()` - Validates credentials, creates session +- `logout()` - Destroys session, redirects to login + +**Session Storage:** +- `admin_id` - Stored in session after successful login +- `admin_username` - Stored for display purposes +- Middleware checks for `admin_id` presence + +#### 2. Admin Routes Structure +**File:** `routes/web.php` + +**Public Routes (no auth):** +- `GET /admin/login` - Show login form +- `POST /admin/login` - Process login + +**Protected Routes (admin.auth middleware):** +- `GET /admin/dashboard` - Main admin dashboard +- `POST /admin/logout` - Logout +- `Resource /admin/docket-entries` - Full CRUD (index, create, store, show, edit, update, destroy) +- `POST /admin/docket-entries/{id}/documents` - Upload document +- `DELETE /admin/documents/{id}` - Delete document +- `GET /admin/subscribers` - List subscribers +- `DELETE /admin/subscribers/{id}` - Delete subscriber + +#### 3. Admin Controller Files Created +**All controllers created but need implementation:** +- `app/Http/Controllers/Admin/DashboardController.php` - Empty, needs index() method +- `app/Http/Controllers/Admin/DocketEntryController.php` - Resource controller, needs all 7 methods +- `app/Http/Controllers/Admin/DocumentController.php` - Empty, needs store() and destroy() +- `app/Http/Controllers/Admin/SubscriberController.php` - Empty, needs index() and destroy() + +### ⏳ Remaining Work + +#### 1. Implement Controller Logic (2-3 hours) + +**DashboardController:** +```php +public function index() +{ + $stats = [ + 'total_entries' => DocketEntry::count(), + 'total_documents' => Document::count(), + 'total_subscribers' => Subscription::where('is_active', true)->count(), + 'recent_entries' => DocketEntry::with('documents')->latest()->take(5)->get(), + ]; + + return Inertia::render('Admin/Dashboard', $stats); +} +``` + +**DocketEntryController (7 methods):** +- `index()` - List all entries with pagination +- `create()` - Show create form +- `store()` - Save new entry +- `show()` - View single entry with documents +- `edit()` - Show edit form +- `update()` - Update entry +- `destroy()` - Delete entry + +**DocumentController:** +- `store()` - Upload PDF, save to storage, create DB record +- `destroy()` - Delete file from storage, remove DB record + +**SubscriberController:** +- `index()` - List all subscribers with pagination +- `destroy()` - Soft delete (set is_active = false) + +#### 2. Create Vue Admin Pages (2-3 hours) + +**Login Page:** +- File: `resources/js/Pages/Admin/Login.vue` +- Form with username/password fields +- CSRF token handling +- Error display +- Submit to POST /admin/login + +**Dashboard Page:** +- File: `resources/js/Pages/Admin/Dashboard.vue` +- Stats cards (entries, documents, subscribers) +- Recent entries list +- Quick action buttons +- Logout button + +**Docket Entry Pages:** +- `resources/js/Pages/Admin/DocketEntries/Index.vue` - List with edit/delete +- `resources/js/Pages/Admin/DocketEntries/Create.vue` - Create form +- `resources/js/Pages/Admin/DocketEntries/Edit.vue` - Edit form +- `resources/js/Pages/Admin/DocketEntries/Show.vue` - View with document upload + +**Subscriber Page:** +- `resources/js/Pages/Admin/Subscribers/Index.vue` - List with delete + +#### 3. Create Admin Seeder (15 min) + +**File:** `database/seeders/AdminSeeder.php` +```php +AdminUser::create([ + 'username' => 'admin', + 'password' => 'password', // Will be auto-hashed by model +]); +``` + +Run: `php artisan db:seed --class=AdminSeeder` + +#### 4. File Storage Configuration (15 min) + +**Update:** `config/filesystems.php` +- Ensure 'public' disk is configured +- Create storage link: `php artisan storage:link` +- Documents will be stored in `storage/app/public/documents/` + +### Next Session Checklist + +**Start Here:** +1. ✅ Read this file first +2. ✅ Review `routes/web.php` to understand route structure +3. ✅ Review `app/Http/Controllers/Admin/AuthController.php` for auth pattern +4. Implement DashboardController->index() +5. Create Admin/Dashboard.vue page +6. Test admin login flow +7. Implement DocketEntryController methods +8. Create docket entry Vue pages +9. Implement DocumentController methods +10. Implement SubscriberController methods +11. Create AdminSeeder +12. Test all admin features +13. Commit Phase 2 complete + +### Key Design Decisions + +**Authentication:** +- Session-based (not token-based) +- Separate from Laravel Breeze User auth +- AdminUser model with password hashing +- Middleware checks session for admin_id + +**File Storage:** +- PDFs stored in `storage/app/public/documents/` +- Accessible via `/storage/documents/{filename}` +- Original filename preserved in DB +- Unique stored filename to prevent conflicts + +**Admin UI:** +- Inertia.js for seamless SPA experience +- Vue 3 + TypeScript +- Tailwind CSS for styling +- Match v1.0 admin dashboard design + +### Testing Commands + +```bash +# Start dev servers +php artisan serve +npm run dev + +# Access admin +http://127.0.0.1:8000/admin/login + +# Create admin user +php artisan db:seed --class=AdminSeeder + +# Test login +Username: admin +Password: password +``` + +### Files to Create Next Session + +1. `app/Http/Controllers/Admin/DashboardController.php` - Implement index() +2. `resources/js/Pages/Admin/Login.vue` +3. `resources/js/Pages/Admin/Dashboard.vue` +4. `resources/js/Pages/Admin/DocketEntries/Index.vue` +5. `resources/js/Pages/Admin/DocketEntries/Create.vue` +6. `resources/js/Pages/Admin/DocketEntries/Edit.vue` +7. `resources/js/Pages/Admin/DocketEntries/Show.vue` +8. `resources/js/Pages/Admin/Subscribers/Index.vue` +9. `database/seeders/AdminSeeder.php` + +### Estimated Time Remaining + +- Controller implementation: 2-3 hours +- Vue pages: 2-3 hours +- Testing & fixes: 1 hour +- **Total: 5-7 hours** + +### Current Git Status + +- Phase 1 committed and pushed ✅ +- Phase 2 authentication foundation ready to commit +- Next commit will be "Phase 2 partial: Admin authentication foundation" diff --git a/routes/web.php b/routes/web.php index cc31d195..f3d53b1c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -18,6 +18,30 @@ Route::get('/api/unsubscribe/{token}', [SubscriptionController::class, 'unsubscr // Document download route Route::get('/api/documents/{id}/download', [DocumentController::class, 'download'])->name('api.documents.download'); +// Admin routes +Route::prefix('admin')->name('admin.')->group(function () { + // Admin login routes (no auth required) + Route::get('/login', [\App\Http\Controllers\Admin\AuthController::class, 'showLogin'])->name('login'); + Route::post('/login', [\App\Http\Controllers\Admin\AuthController::class, 'login'])->name('login.post'); + + // Protected admin routes + Route::middleware('admin.auth')->group(function () { + Route::get('/dashboard', [\App\Http\Controllers\Admin\DashboardController::class, 'index'])->name('dashboard'); + Route::post('/logout', [\App\Http\Controllers\Admin\AuthController::class, 'logout'])->name('logout'); + + // Docket entry routes + Route::resource('docket-entries', \App\Http\Controllers\Admin\DocketEntryController::class); + + // Document routes + Route::post('/docket-entries/{docketEntry}/documents', [\App\Http\Controllers\Admin\DocumentController::class, 'store'])->name('documents.store'); + Route::delete('/documents/{document}', [\App\Http\Controllers\Admin\DocumentController::class, 'destroy'])->name('documents.destroy'); + + // Subscriber routes + Route::get('/subscribers', [\App\Http\Controllers\Admin\SubscriberController::class, 'index'])->name('subscribers.index'); + Route::delete('/subscribers/{subscription}', [\App\Http\Controllers\Admin\SubscriberController::class, 'destroy'])->name('subscribers.destroy'); + }); +}); + Route::get('/dashboard', function () { return Inertia::render('Dashboard'); })->middleware(['auth', 'verified'])->name('dashboard');