create-prisma-php-app 3.1.11 → 4.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/bootstrap.php +10 -10
  2. package/dist/index.js +12 -8
  3. package/dist/settings/restart-websocket.bat +1 -1
  4. package/dist/settings/restart-websocket.ts +2 -9
  5. package/dist/src/{Lib/Websocket → Websocket}/ConnectionManager.php +1 -1
  6. package/dist/src/app/error.php +1 -1
  7. package/dist/src/app/index.php +1 -1
  8. package/dist/src/app/js/index.js +1 -1
  9. package/dist/src/app/layout.php +2 -2
  10. package/dist/{src/Lib/Websocket/websocket-server.php → websocket-server.php} +2 -7
  11. package/package.json +1 -1
  12. package/dist/src/Lib/AI/ChatGPTClient.php +0 -147
  13. package/dist/src/Lib/Auth/Auth.php +0 -544
  14. package/dist/src/Lib/Auth/AuthConfig.php +0 -89
  15. package/dist/src/Lib/CacheHandler.php +0 -121
  16. package/dist/src/Lib/ErrorHandler.php +0 -322
  17. package/dist/src/Lib/FileManager/UploadFile.php +0 -383
  18. package/dist/src/Lib/Headers/Boom.php +0 -208
  19. package/dist/src/Lib/IncludeTracker.php +0 -59
  20. package/dist/src/Lib/MainLayout.php +0 -215
  21. package/dist/src/Lib/Middleware/AuthMiddleware.php +0 -154
  22. package/dist/src/Lib/PHPMailer/Mailer.php +0 -169
  23. package/dist/src/Lib/PHPX/Exceptions/ComponentValidationException.php +0 -49
  24. package/dist/src/Lib/PHPX/IPHPX.php +0 -22
  25. package/dist/src/Lib/PHPX/PHPX.php +0 -173
  26. package/dist/src/Lib/PHPX/TemplateCompiler.php +0 -571
  27. package/dist/src/Lib/PHPX/TwMerge.php +0 -195
  28. package/dist/src/Lib/PHPX/TypeCoercer.php +0 -490
  29. package/dist/src/Lib/PartialRenderer.php +0 -40
  30. package/dist/src/Lib/PrismaPHPSettings.php +0 -181
  31. package/dist/src/Lib/Request.php +0 -476
  32. package/dist/src/Lib/Set.php +0 -102
  33. package/dist/src/Lib/StateManager.php +0 -127
  34. package/dist/src/Lib/Validator.php +0 -738
@@ -1,89 +0,0 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Lib\Auth;
6
-
7
- use ArrayObject;
8
-
9
- enum AuthRole: string
10
- {
11
- case Admin = 'Admin';
12
- case User = 'User';
13
-
14
- public function equals($role)
15
- {
16
- return $this->value === $role;
17
- }
18
- }
19
-
20
- final class AuthConfig
21
- {
22
- public const ROLE_IDENTIFIER = 'role';
23
- public const IS_ROLE_BASE = false;
24
- public const IS_TOKEN_AUTO_REFRESH = false;
25
- public const IS_ALL_ROUTES_PRIVATE = false;
26
-
27
- /**
28
- * This is the (default) option for authentication. If IS_ALL_ROUTES_PRIVATE is set to false,
29
- * An array of private routes that are accessible to all authenticated users
30
- * without specific role-based access control. Routes should be listed as string paths.
31
- * Example: public static $privateRoutes = ['/']; // This makes the home page private
32
- * Example: public static $privateRoutes = ['/profile', '/dashboard/settings']; // These routes are private
33
- */
34
- public static array $privateRoutes = [];
35
-
36
- /**
37
- * This is the (default) option for authentication. If IS_ALL_ROUTES_PRIVATE is set to true,
38
- * An array of public routes that are accessible to all users, authenticated or not.
39
- */
40
- public const DEFAULT_SIGNIN_REDIRECT = '/dashboard'; // Default redirect route after sign in
41
- public const API_AUTH_PREFIX = '/api/auth'; // Prefix for third-party API authentication routes (github, google, etc.)
42
-
43
- /**
44
- * An array of public routes that are accessible to all users, authenticated or not.
45
- * Routes should be listed as string paths.
46
- * Example: public static $publicRoutes = ['/']; // This makes the home page public
47
- * Example: public static $publicRoutes = ['/about', '/contact']; // These routes are public
48
- */
49
- public static array $publicRoutes = ['/'];
50
- public static array $authRoutes = [
51
- '/signin',
52
- '/signup',
53
- ];
54
-
55
- /**
56
- * An associative array mapping specific routes to required user roles for access control.
57
- * Each route is a key with an array of roles that are allowed access.
58
- * Format:
59
- * 'route_path' => [self::ROLE_IDENTIFIER => [AuthRole::Role1, AuthRole::Role2, ...]],
60
- * Example:
61
- * public static $roleBasedRoutes = [
62
- * 'dashboard' => [self::ROLE_IDENTIFIER => [AuthRole::Admin, AuthRole::User]],
63
- * 'dashboard/users' => [self::ROLE_IDENTIFIER => [AuthRole::Admin]],
64
- * 'sales' => [self::ROLE_IDENTIFIER => [AuthRole::Admin, AuthRole::User]]
65
- * ];
66
- */
67
- public static array $roleBasedRoutes = [];
68
-
69
- /**
70
- * Checks if the given user role is authorized to access a set of roles.
71
- *
72
- * @param ArrayObject|string $userRole The user's role to check.
73
- * @param array<AuthRole> $roles An array of AuthRole instances specifying allowed roles.
74
- * @return bool Returns true if the user's role matches any of the allowed roles, false otherwise.
75
- */
76
- public static function checkAuthRole(ArrayObject|string $userRole, array $roles): bool
77
- {
78
- if ($userRole instanceof ArrayObject) {
79
- $userRole = $userRole[Auth::ROLE_NAME] ?? '';
80
- }
81
-
82
- foreach ($roles as $role) {
83
- if ($userRole === $role->value) {
84
- return true;
85
- }
86
- }
87
- return false;
88
- }
89
- }
@@ -1,121 +0,0 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Lib;
6
-
7
- use Lib\PrismaPHPSettings;
8
-
9
- class CacheHandler
10
- {
11
- public static bool $isCacheable = true; // Enable or disable caching by route
12
- public static int $ttl = 0; // Time to live in seconds (0 = no action taken)
13
-
14
- private static string $cacheDir = DOCUMENT_PATH . '/caches';
15
- private static bool $cacheDirChecked = false;
16
-
17
- private static function ensureCacheDirectoryExists(): void
18
- {
19
- if (self::$cacheDirChecked) {
20
- return;
21
- }
22
-
23
- if (!is_dir(self::$cacheDir) && !mkdir(self::$cacheDir, 0777, true) && !is_dir(self::$cacheDir)) {
24
- die("Error: Unable to create cache directory at: " . self::$cacheDir);
25
- }
26
-
27
- self::$cacheDirChecked = true;
28
- }
29
-
30
- public static function getCacheFilePath(string $uri): string
31
- {
32
- $requestFilesData = PrismaPHPSettings::$includeFiles;
33
- $fileName = $requestFilesData[$uri]['fileName'] ?? '';
34
- $isCacheable = $requestFilesData[$uri]['isCacheable'] ?? self::$isCacheable;
35
-
36
- if (!$isCacheable || $fileName === '') {
37
- return '';
38
- }
39
-
40
- return self::$cacheDir . '/' . $fileName . '.html';
41
- }
42
-
43
- private static function isExpired(string $cacheFile, int $ttlSeconds = 600): bool
44
- {
45
- if (!file_exists($cacheFile)) {
46
- return true;
47
- }
48
- $fileAge = time() - filemtime($cacheFile);
49
- return $fileAge > $ttlSeconds;
50
- }
51
-
52
- /**
53
- * Serve cache if available, not expired, and route is marked cacheable.
54
- * We look up a route-specific TTL if defined, otherwise use a fallback.
55
- */
56
- public static function serveCache(string $uri, int $defaultTtl = 600): void
57
- {
58
- if ($uri === '') {
59
- return;
60
- }
61
-
62
- $requestFilesData = PrismaPHPSettings::$includeFiles;
63
-
64
- // Get the route-specific TTL if set, or default to 0
65
- $routeTtl = $requestFilesData[$uri]['cacheTtl'] ?? 0;
66
-
67
- // If the route has a TTL greater than 0, use that.
68
- // Otherwise (0 or not defined), use the default.
69
- $ttlSeconds = ($routeTtl > 0) ? $routeTtl : $defaultTtl;
70
-
71
- $cacheFile = self::getCacheFilePath($uri);
72
-
73
- if ($cacheFile === '' || !file_exists($cacheFile) || self::isExpired($cacheFile, $ttlSeconds)) {
74
- return;
75
- }
76
-
77
- echo "<!-- Cached copy generated at: " . date('Y-m-d H:i:s', filemtime($cacheFile)) . " -->\n";
78
- readfile($cacheFile);
79
- exit;
80
- }
81
-
82
- public static function saveCache(string $uri, string $content, bool $useLock = true): void
83
- {
84
- if ($uri === '') {
85
- return;
86
- }
87
- self::ensureCacheDirectoryExists();
88
-
89
- $cacheFile = self::getCacheFilePath($uri);
90
- if ($cacheFile === '') {
91
- return;
92
- }
93
-
94
- $flags = $useLock ? LOCK_EX : 0;
95
- $written = @file_put_contents($cacheFile, $content, $flags);
96
-
97
- if ($written === false) {
98
- die("Error: Failed to write cache file: $cacheFile");
99
- }
100
- }
101
-
102
- public static function resetCache(?string $uri = null): void
103
- {
104
- self::ensureCacheDirectoryExists();
105
-
106
- if ($uri !== null) {
107
- $cacheFile = self::getCacheFilePath($uri);
108
- if ($cacheFile !== '' && file_exists($cacheFile)) {
109
- unlink($cacheFile);
110
- }
111
- return;
112
- }
113
-
114
- $files = glob(self::$cacheDir . '/*.html') ?: [];
115
- foreach ($files as $file) {
116
- if (is_file($file)) {
117
- unlink($file);
118
- }
119
- }
120
- }
121
- }
@@ -1,322 +0,0 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Lib;
6
-
7
- use Bootstrap;
8
- use Lib\MainLayout;
9
- use Throwable;
10
- use Lib\PHPX\Exceptions\ComponentValidationException;
11
-
12
- class ErrorHandler
13
- {
14
- public static string $content = '';
15
-
16
- public static function registerHandlers(): void
17
- {
18
- self::registerExceptionHandler();
19
- self::registerShutdownFunction();
20
- self::registerErrorHandler();
21
- }
22
-
23
- private static function registerExceptionHandler(): void
24
- {
25
- set_exception_handler(function ($exception) {
26
- $errorContent = Bootstrap::isAjaxOrXFileRequestOrRouteFile()
27
- ? "Exception: " . $exception->getMessage()
28
- : "<div class='error'>Exception: " . htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8') . "</div>";
29
-
30
- self::modifyOutputLayoutForError($errorContent);
31
- });
32
- }
33
-
34
- private static function registerShutdownFunction(): void
35
- {
36
- register_shutdown_function(function () {
37
- $error = error_get_last();
38
- if (
39
- $error !== null &&
40
- in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_RECOVERABLE_ERROR], true)
41
- ) {
42
- $errorContent = Bootstrap::isAjaxOrXFileRequestOrRouteFile()
43
- ? "Fatal Error: " . $error['message'] . " in " . $error['file'] . " on line " . $error['line']
44
- : "<div class='error'>Fatal Error: " . htmlspecialchars($error['message'], ENT_QUOTES, 'UTF-8') .
45
- " in " . htmlspecialchars($error['file'], ENT_QUOTES, 'UTF-8') .
46
- " on line " . $error['line'] . "</div>";
47
-
48
- self::modifyOutputLayoutForError($errorContent);
49
- }
50
- });
51
- }
52
-
53
- private static function registerErrorHandler(): void
54
- {
55
- set_error_handler(function ($severity, $message, $file, $line) {
56
- if (!(error_reporting() & $severity)) {
57
- return;
58
- }
59
- $errorContent = Bootstrap::isAjaxOrXFileRequestOrRouteFile()
60
- ? "Error: {$severity} - {$message} in {$file} on line {$line}"
61
- : "<div class='error'>Error: {$message} in {$file} on line {$line}</div>";
62
-
63
- if ($severity === E_WARNING || $severity === E_NOTICE) {
64
- self::modifyOutputLayoutForError($errorContent);
65
- }
66
- });
67
- }
68
-
69
- public static function checkFatalError(): void
70
- {
71
- $error = error_get_last();
72
- if (
73
- $error !== null &&
74
- in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_RECOVERABLE_ERROR], true)
75
- ) {
76
- $errorContent = Bootstrap::isAjaxOrXFileRequestOrRouteFile()
77
- ? "Fatal Error: " . $error['message'] . " in " . $error['file'] . " on line " . $error['line']
78
- : "<div class='error'>Fatal Error: " . htmlspecialchars($error['message'], ENT_QUOTES, 'UTF-8') .
79
- " in " . htmlspecialchars($error['file'], ENT_QUOTES, 'UTF-8') .
80
- " on line " . $error['line'] . "</div>";
81
-
82
- self::modifyOutputLayoutForError($errorContent);
83
- }
84
- }
85
-
86
- public static function modifyOutputLayoutForError($contentToAdd): void
87
- {
88
- $errorFile = APP_PATH . '/error.php';
89
- $errorFileExists = file_exists($errorFile);
90
-
91
- if ($_ENV['SHOW_ERRORS'] === "false") {
92
- if ($errorFileExists) {
93
- $contentToAdd = Bootstrap::isAjaxOrXFileRequestOrRouteFile() ? "An error occurred" : "<div class='error'>An error occurred</div>";
94
- } else {
95
- exit;
96
- }
97
- }
98
-
99
- if ($errorFileExists) {
100
- if (ob_get_level()) {
101
- ob_end_clean();
102
- }
103
- self::$content = $contentToAdd;
104
- if (Bootstrap::isAjaxOrXFileRequestOrRouteFile()) {
105
- header('Content-Type: application/json');
106
- echo json_encode(['success' => false, 'error' => self::$content]);
107
- http_response_code(403);
108
- } else {
109
- $layoutFile = APP_PATH . '/layout.php';
110
- if (file_exists($layoutFile)) {
111
- ob_start();
112
- require_once $errorFile;
113
- MainLayout::$children = ob_get_clean();
114
- require $layoutFile;
115
- } else {
116
- echo self::$content;
117
- }
118
- }
119
- } else {
120
- if (Bootstrap::isAjaxOrXFileRequestOrRouteFile()) {
121
- header('Content-Type: application/json');
122
- echo json_encode(['success' => false, 'error' => $contentToAdd]);
123
- http_response_code(403);
124
- } else {
125
- echo $contentToAdd;
126
- }
127
- }
128
- exit;
129
- }
130
-
131
- public static function formatExceptionForDisplay(Throwable $exception): string
132
- {
133
- // Handle specific exception types
134
- if ($exception instanceof ComponentValidationException) {
135
- return self::formatComponentValidationError($exception);
136
- }
137
-
138
- // Handle template compilation errors specifically
139
- if (strpos($exception->getMessage(), 'Invalid prop') !== false) {
140
- return self::formatTemplateCompilerError($exception);
141
- }
142
-
143
- // Generic exception formatting
144
- return self::formatGenericException($exception);
145
- }
146
-
147
- private static function formatComponentValidationError(ComponentValidationException $exception): string
148
- {
149
- $message = htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8');
150
- $file = htmlspecialchars($exception->getFile(), ENT_QUOTES, 'UTF-8');
151
- $line = $exception->getLine();
152
-
153
- // Get the details from the ComponentValidationException
154
- $propName = method_exists($exception, 'getPropName') ? $exception->getPropName() : 'unknown';
155
- $componentName = method_exists($exception, 'getComponentName') ? $exception->getComponentName() : 'unknown';
156
- $availableProps = method_exists($exception, 'getAvailableProps') ? $exception->getAvailableProps() : [];
157
-
158
- $availablePropsString = !empty($availableProps) ? implode(', ', $availableProps) : 'none defined';
159
-
160
- return <<<HTML
161
- <div class="error-container max-w-4xl mx-auto mt-8 bg-red-50 border border-red-200 rounded-lg shadow-lg">
162
- <div class="bg-red-100 px-6 py-4 border-b border-red-200">
163
- <h2 class="text-xl font-bold text-red-800 flex items-center">
164
- <svg class="w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20">
165
- <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
166
- </svg>
167
- Component Validation Error
168
- </h2>
169
- </div>
170
-
171
- <div class="p-6">
172
- <div class="bg-white border border-red-200 rounded-lg p-4 mb-4">
173
- <div class="mb-3">
174
- <span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium">
175
- Component: {$componentName}
176
- </span>
177
- <span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium ml-2">
178
- Invalid Prop: {$propName}
179
- </span>
180
- </div>
181
- <pre class="text-sm text-red-800 whitespace-pre-wrap font-mono">{$message}</pre>
182
- </div>
183
-
184
- <div class="text-sm text-gray-600 mb-4">
185
- <strong>File:</strong> <code class="bg-gray-100 px-2 py-1 rounded text-xs">{$file}</code><br />
186
- <strong>Line:</strong> <span class="bg-gray-100 px-2 py-1 rounded text-xs">{$line}</span>
187
- </div>
188
-
189
- <div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
190
- <h4 class="font-medium text-blue-800 mb-2">💡 Available Props:</h4>
191
- <p class="text-blue-700 text-sm">
192
- <code class="bg-blue-100 px-2 py-1 rounded text-xs">{$availablePropsString}</code>
193
- </p>
194
- </div>
195
-
196
- <div class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4">
197
- <h4 class="font-medium text-green-800 mb-2">🔧 Quick Fixes:</h4>
198
- <ul class="text-green-700 text-sm space-y-1">
199
- <li>• Remove the '<code>{$propName}</code>' prop from your template</li>
200
- <li>• Add '<code>public \${$propName};</code>' to your <code>{$componentName}</code> component class</li>
201
- <li>• Use data attributes: '<code>data-{$propName}</code>' instead</li>
202
- </ul>
203
- </div>
204
-
205
- <details class="mt-4">
206
- <summary class="cursor-pointer text-red-600 font-medium hover:text-red-800 select-none">
207
- Show Stack Trace
208
- </summary>
209
- <div class="mt-3 bg-gray-50 border border-gray-200 rounded p-4">
210
- <pre class="text-xs text-gray-700 overflow-auto whitespace-pre-wrap max-h-96">{$exception->getTraceAsString()}</pre>
211
- </div>
212
- </details>
213
- </div>
214
- </div>
215
- HTML;
216
- }
217
-
218
- private static function formatTemplateCompilerError(Throwable $exception): string
219
- {
220
- $message = htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8');
221
- $file = htmlspecialchars($exception->getFile(), ENT_QUOTES, 'UTF-8');
222
- $line = $exception->getLine();
223
-
224
- // Extract the component validation error details
225
- if (preg_match("/Invalid prop '([^']+)' passed to component '([^']+)'/", $exception->getMessage(), $matches)) {
226
- $invalidProp = $matches[1];
227
- $componentName = $matches[2];
228
-
229
- return <<<HTML
230
- <div class="error-container max-w-4xl mx-auto mt-8 bg-red-50 border border-red-200 rounded-lg shadow-lg">
231
- <div class="bg-red-100 px-6 py-4 border-b border-red-200">
232
- <h2 class="text-xl font-bold text-red-800 flex items-center">
233
- <svg class="w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20">
234
- <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
235
- </svg>
236
- Template Compilation Error
237
- </h2>
238
- </div>
239
-
240
- <div class="p-6">
241
- <div class="bg-white border border-red-200 rounded-lg p-4 mb-4">
242
- <div class="mb-3">
243
- <span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium">
244
- Component: {$componentName}
245
- </span>
246
- <span class="inline-block bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium ml-2">
247
- Invalid Prop: {$invalidProp}
248
- </span>
249
- </div>
250
- <p class="text-red-800 font-medium">{$message}</p>
251
- </div>
252
-
253
- <div class="text-sm text-gray-600 mb-4">
254
- <strong>File:</strong> {$file}<br />
255
- <strong>Line:</strong> {$line}
256
- </div>
257
-
258
- <div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
259
- <h4 class="font-medium text-blue-800 mb-2">💡 Quick Fix:</h4>
260
- <p class="text-blue-700 text-sm">
261
- Either remove the '<code>{$invalidProp}</code>' prop from your template, or add it as a public property to your <code>{$componentName}</code> component class.
262
- </p>
263
- </div>
264
-
265
- <details class="mt-4">
266
- <summary class="cursor-pointer text-red-600 font-medium hover:text-red-800 select-none">
267
- Show Stack Trace
268
- </summary>
269
- <div class="mt-3 bg-gray-50 border border-gray-200 rounded p-4">
270
- <pre class="text-xs text-gray-700 overflow-auto whitespace-pre-wrap">{$exception->getTraceAsString()}</pre>
271
- </div>
272
- </details>
273
- </div>
274
- </div>
275
- HTML;
276
- }
277
-
278
- // Fallback to generic formatting
279
- return self::formatGenericException($exception);
280
- }
281
-
282
- private static function formatGenericException(Throwable $exception): string
283
- {
284
- $type = htmlspecialchars(get_class($exception), ENT_QUOTES, 'UTF-8');
285
- $message = htmlspecialchars($exception->getMessage(), ENT_QUOTES, 'UTF-8');
286
- $file = htmlspecialchars($exception->getFile(), ENT_QUOTES, 'UTF-8');
287
- $line = $exception->getLine();
288
-
289
- return <<<HTML
290
- <div class="error-container max-w-4xl mx-auto mt-8 bg-red-50 border border-red-200 rounded-lg shadow-lg">
291
- <div class="bg-red-100 px-6 py-4 border-b border-red-200">
292
- <h2 class="text-xl font-bold text-red-800 flex items-center">
293
- <svg class="w-6 h-6 mr-2" fill="currentColor" viewBox="0 0 20 20">
294
- <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
295
- </svg>
296
- {$type}
297
- </h2>
298
- </div>
299
-
300
- <div class="p-6">
301
- <div class="bg-white border border-red-200 rounded-lg p-4 mb-4">
302
- <p class="text-red-800 font-medium break-words">{$message}</p>
303
- </div>
304
-
305
- <div class="text-sm text-gray-600 mb-4">
306
- <strong>File:</strong> <code class="bg-gray-100 px-2 py-1 rounded text-xs">{$file}</code><br />
307
- <strong>Line:</strong> <span class="bg-gray-100 px-2 py-1 rounded text-xs">{$line}</span>
308
- </div>
309
-
310
- <details class="mt-4">
311
- <summary class="cursor-pointer text-red-600 font-medium hover:text-red-800 select-none">
312
- Show Stack Trace
313
- </summary>
314
- <div class="mt-3 bg-gray-50 border border-gray-200 rounded p-4">
315
- <pre class="text-xs text-gray-700 overflow-auto whitespace-pre-wrap max-h-96">{$exception->getTraceAsString()}</pre>
316
- </div>
317
- </details>
318
- </div>
319
- </div>
320
- HTML;
321
- }
322
- }