# Dashboard Error Page Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use compose:subagent (recommended) or compose:execute to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Create a dedicated error page in the admin dashboard that shows full error context (status code, URL, message, stack trace, request params, user info, timestamp) with access restricted to super_admin users. **Architecture:** Modify `Handler.php` to detect dashboard routes and render `Dashboard/DashboardError.vue` instead of `Error.vue`. The new component uses `DashboardLayout` and displays full error context as props from the backend. **Tech Stack:** Laravel 10+, Vue 3 Composition API, Inertia.js, Tailwind CSS, Porto Architecture ## Global Constraints - Code in English, comments in English - Composition API (` ``` - [ ] **Step 2: Verify component structure** Run: `grep -c "DashboardLayout" resources/js/Pages/Dashboard/DashboardError.vue` Expected: `2` (import + template usage) - [ ] **Step 3: Commit** ```bash git add resources/js/Pages/Dashboard/DashboardError.vue git commit -m "feat(dashboard): add DashboardError.vue with full error context display" ``` --- ### Task 2: Modify Exception Handler for Dashboard Routes **Covers:** S1, S2 (Route detection, context gathering) **Files:** - Modify: `app/Exceptions/Handler.php` - Modify: `app/Ship/Exceptions/Handler.php` **Interfaces:** - Consumes: Request object, exception, user roles - Produces: Inertia render with Dashboard/DashboardError.vue props - [ ] **Step 1: Add helper method and modify render in App Handler** Replace the `render` method in `app/Exceptions/Handler.php`: ```php , \Psr\Log\LogLevel::*> */ protected $levels = [ // ]; /** * A list of the exception types that are not reported. * * @var array> */ protected $dontReport = [ // ]; /** * A list of the inputs that are never flashed to the session on validation exceptions. * * @var array */ protected $dontFlash = [ 'current_password', 'password', 'password_confirmation', ]; /** * Register the exception handling callbacks for the application. */ public function register(): void { $this->reportable(function (Throwable $e) { // }); } /** * Prepare exception for rendering. * * @param \Throwable $e * @return \Throwable */ public function render($request, Throwable $e) { $response = parent::render($request, $e); $statusCode = $response->getStatusCode(); if (in_array($statusCode, [500, 503, 404, 403])) { if ($this->isDashboardRequest($request)) { return $this->renderDashboardError($request, $e, $statusCode); } if (! app()->environment(['local', 'testing'])) { return Inertia::render('Error', ['status' => $statusCode]) ->toResponse($request) ->setStatusCode($statusCode); } } elseif ($statusCode === 419) { return back()->with([ 'message' => 'The page expired, please try again.', ]); } return $response; } /** * Check if the request is for a dashboard route. */ private function isDashboardRequest(Request $request): bool { return str_starts_with($request->path(), 'dashboard'); } /** * Render error page with full context for dashboard users. */ private function renderDashboardError(Request $request, Throwable $e, int $statusCode) { $user = $request->user(); $isSuperAdmin = $user && $user->hasRole('super_admin'); $isProduction = app()->environment('production'); $props = [ 'status' => $statusCode, 'message' => $this->getErrorMessage($e, $statusCode), 'url' => $request->fullUrl(), 'method' => $request->method(), 'user' => $user ? $user->only('id', 'name', 'email') : null, 'timestamp' => now()->toIso8601String(), ]; // Only superadmins get stack trace and request params if ($isSuperAdmin && !$isProduction) { $props['stackTrace'] = $e->getTraceAsString(); $props['requestParams'] = $request->except([ 'password', 'password_confirmation', 'current_password', '_token', ]); } return Inertia::render('Dashboard/DashboardError', $props) ->toResponse($request) ->setStatusCode($statusCode); } /** * Get a user-friendly error message based on status code. */ private function getErrorMessage(Throwable $e, int $statusCode): string { $messages = [ 503 => 'Сервис временно недоступен. Попробуйте позже.', 500 => 'Внутренняя ошибка сервера.', 404 => 'Запрашиваемая страница не найдена.', 403 => 'У вас нет доступа к этой странице.', ]; return $messages[$statusCode] ?? 'Произошла неизвестная ошибка.'; } } ``` - [ ] **Step 2: Apply same changes to Ship Handler** Replace the `render` method in `app/Ship/Exceptions/Handler.php`: ```php , \Psr\Log\LogLevel::*> */ protected $levels = [ // ]; /** * A list of the exception types that are not reported. * * @var array> */ protected $dontReport = [ // ]; /** * A list of the inputs that are never flashed to the session on validation exceptions. * * @var array */ protected $dontFlash = [ 'current_password', 'password', 'password_confirmation', ]; /** * Register the exception handling callbacks for the application. * * @return void */ public function register() { $this->reportable(function (Throwable $e) { // }); } public function render($request, Throwable $e) { $response = parent::render($request, $e); $statusCode = $response->getStatusCode(); if (in_array($statusCode, [500, 503, 404, 403])) { if ($this->isDashboardRequest($request)) { return $this->renderDashboardError($request, $e, $statusCode); } if (! app()->environment(['local', 'testing'])) { return Inertia::render('Error', ['status' => $statusCode]) ->toResponse($request) ->setStatusCode($statusCode); } } elseif ($statusCode === 419) { return back()->with([ 'message' => 'The page expired, please try again.', ]); } return $response; } /** * Check if the request is for a dashboard route. */ private function isDashboardRequest(Request $request): bool { return str_starts_with($request->path(), 'dashboard'); } /** * Render error page with full context for dashboard users. */ private function renderDashboardError(Request $request, Throwable $e, int $statusCode) { $user = $request->user(); $isSuperAdmin = $user && $user->hasRole('super_admin'); $isProduction = app()->environment('production'); $props = [ 'status' => $statusCode, 'message' => $this->getErrorMessage($e, $statusCode), 'url' => $request->fullUrl(), 'method' => $request->method(), 'user' => $user ? $user->only('id', 'name', 'email') : null, 'timestamp' => now()->toIso8601String(), ]; // Only superadmins get stack trace and request params if ($isSuperAdmin && !$isProduction) { $props['stackTrace'] = $e->getTraceAsString(); $props['requestParams'] = $request->except([ 'password', 'password_confirmation', 'current_password', '_token', ]); } return Inertia::render('Dashboard/DashboardError', $props) ->toResponse($request) ->setStatusCode($statusCode); } /** * Get a user-friendly error message based on status code. */ private function getErrorMessage(Throwable $e, int $statusCode): string { $messages = [ 503 => 'Сервис временно недоступен. Попробуйте позже.', 500 => 'Внутренняя ошибка сервера.', 404 => 'Запрашиваемая страница не найдена.', 403 => 'У вас нет доступа к этой странице.', ]; return $messages[$statusCode] ?? 'Произошла неизвестная ошибка.'; } } ``` - [ ] **Step 3: Verify PHP syntax** Run: `docker exec ntspi-php php -l app/Exceptions/Handler.php && docker exec ntspi-php php -l app/Ship/Exceptions/Handler.php` Expected: No syntax errors - [ ] **Step 4: Commit** ```bash git add app/Exceptions/Handler.php app/Ship/Exceptions/Handler.php git commit -m "feat(dashboard): modify exception handlers to render DashboardError for dashboard routes" ``` --- ### Task 3: Verify End-to-End Flow **Covers:** S1, S2, S3 (Full verification) **Files:** - Verify: `resources/js/Pages/Dashboard/DashboardError.vue` - Verify: `app/Exceptions/Handler.php` - Verify: `app/Ship/Exceptions/Handler.php` - [ ] **Step 1: Test 404 on dashboard route** Navigate to: `http://localhost/dashboard/nonexistent-page` Expected: DashboardError page with status 404, dashboard layout visible - [ ] **Step 2: Test 403 on dashboard route (non-superadmin)** Login as non-superadmin user, navigate to restricted route Expected: DashboardError page with status 403, no stack trace or params - [ ] **Step 3: Test 403 on dashboard route (superadmin)** Login as superadmin, trigger 403 Expected: DashboardError page with status 403, stack trace and params visible - [ ] **Step 4: Test public site error (unchanged)** Navigate to non-existent public page Expected: Original Error.vue with public layout - [ ] **Step 5: Final commit (if any fixes needed)** ```bash git add -A git commit -m "fix(dashboard): address review feedback on error page" ```