diff --git a/app/Exceptions/ErrorToastException.php b/app/Exceptions/ErrorToastException.php
new file mode 100644
index 00000000..900a5eb4
--- /dev/null
+++ b/app/Exceptions/ErrorToastException.php
@@ -0,0 +1,10 @@
+withRouting(
@@ -35,17 +37,53 @@
})
->withExceptions(function (Exceptions $exceptions) {
$exceptions->respond(function (Response $response, Throwable $exception, Request $request) {
- if (
- !app()->environment(['local', 'testing'])
- && in_array($response->getStatusCode(), [500, 503, 404, 403])
- ) {
- return Inertia::render('Error', [
- 'homepageRoute' => route('welcome'),
- 'status' => $response->getStatusCode()
- ])
- ->toResponse($request)
- ->setStatusCode($response->getStatusCode());
- } elseif ($response->getStatusCode() === 419) {
+ $statusCode = $response->getStatusCode();
+ $errorTitles = [
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 500 => 'Server Error',
+ 503 => 'Service Unavailable',
+ ];
+ $errorDetails = [
+ 403 => 'Sorry, you are unauthorized to access this resource/action.',
+ 404 => 'Sorry, the resource you are looking for could not be found.',
+ 500 => 'Whoops, something went wrong on our end. Please try again.',
+ 503 => 'Sorry, we are doing some maintenance. Please check back soon.',
+ ];
+
+ if (in_array($statusCode, [500, 503, 404, 403])) {
+ if (!$request->inertia()) {
+ // Show error page component for standard visits
+ return Inertia::render('Error', [
+ 'errorTitles' => $errorTitles,
+ 'errorDetails' => $errorDetails,
+ 'status' => $statusCode,
+ 'homepageRoute' => route('welcome'),
+ 'ziggy' => fn () => [
+ ...(new Ziggy())->toArray(),
+ 'location' => $request->url(),
+ ],
+ ])
+ ->toResponse($request)
+ ->setStatusCode($statusCode);
+ } else {
+ // Show standard modal for easier debugging locally
+ if (app()->isLocal() && $statusCode === 500) {
+ return $response;
+ }
+ // Return JSON response for PrimeVue toast to display, handled by Inertia router event listener
+ $errorSummary = "$statusCode - $errorTitles[$statusCode]";
+ $errorDetail = $errorDetails[$statusCode];
+ if (get_class($exception) === ErrorToastException::class) {
+ $errorSummary = "$statusCode - Error";
+ $errorDetail = $exception->getMessage();
+ }
+ return response()->json([
+ 'error_summary' => $errorSummary,
+ 'error_detail' => $errorDetail,
+ ], $statusCode);
+ }
+ } elseif ($statusCode === 419) {
return back()->with([
'flash_warn' => 'The page expired, please try again.',
]);
diff --git a/resources/css/tailwind.css b/resources/css/tailwind.css
index 7b6606f0..be494fff 100644
--- a/resources/css/tailwind.css
+++ b/resources/css/tailwind.css
@@ -6,6 +6,7 @@
@source '../../storage/framework/views/*.php';
@source '../../resources/views/**/*.blade.php';
@source '../../resources/js/**/*.vue';
+@source '../../resources/js/theme/*.js';
@custom-variant dark (&:where(.dark, .dark *));
diff --git a/resources/js/app.js b/resources/js/app.js
index 187eb474..296bbc58 100644
--- a/resources/js/app.js
+++ b/resources/js/app.js
@@ -2,18 +2,21 @@ import '../css/app.css';
import '../css/tailwind.css';
import { createSSRApp, h } from 'vue';
-import { createInertiaApp, Head, Link } from '@inertiajs/vue3';
+import { createInertiaApp, router, Head, Link } from '@inertiajs/vue3';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy';
import PrimeVue from 'primevue/config';
import ToastService from 'primevue/toastservice';
+import { useToast } from 'primevue/usetoast';
+import Toast from 'primevue/toast';
import Container from '@/components/Container.vue';
import PageTitleSection from '@/components/PageTitleSection.vue';
import { useSiteColorMode } from '@/composables/useSiteColorMode';
import themePreset from '@/theme/noir-preset';
+import globalPt from '@/theme/global-pt';
/* global Ziggy */
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
@@ -29,7 +32,32 @@ createInertiaApp({
// Site light/dark mode
const colorMode = useSiteColorMode({ emitAuto: true });
- const app = createSSRApp({ render: () => h(App, props) })
+ // Global Toast component
+ const Root = {
+ setup() {
+ // show error toast instead of standard Inertia modal response
+ const toast = useToast();
+ router.on('invalid', (event) => {
+ const responseBody = event.detail.response?.data;
+ if (responseBody?.error_summary && responseBody?.error_detail) {
+ event.preventDefault();
+ toast.add({
+ severity: event.detail.response?.status >= 500 ? 'error' : 'warn',
+ summary: responseBody.error_summary,
+ detail: responseBody.error_detail,
+ life: 5000,
+ });
+ }
+ });
+
+ return () => h('div', [
+ h(App, props),
+ h(Toast, { position: 'bottom-right' })
+ ]);
+ }
+ };
+
+ const app = createSSRApp(Root)
.use(plugin)
.use(ZiggyVue, Ziggy)
.use(PrimeVue, {
@@ -43,6 +71,7 @@ createInertiaApp({
},
},
},
+ pt: globalPt,
})
.use(ToastService)
.component('InertiaHead', Head)
diff --git a/resources/js/layouts/app/HeaderLayout.vue b/resources/js/layouts/app/HeaderLayout.vue
index a08e383f..b5abe504 100644
--- a/resources/js/layouts/app/HeaderLayout.vue
+++ b/resources/js/layouts/app/HeaderLayout.vue
@@ -82,7 +82,6 @@ const toggleMobileUserMenu = (event) => {
-
- {{ description }} +
+ {{ details }}