Skip to content

Pull master #350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jun 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/Exceptions/ErrorToastException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace App\Exceptions;

use Exception;

class ErrorToastException extends Exception
{
//
}
60 changes: 49 additions & 11 deletions bootstrap/app.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use App\Exceptions\ErrorToastException;
use App\Http\Middleware\EncryptCookies;
use App\Http\Middleware\HandleInertiaRequests;
use Illuminate\Cookie\Middleware\EncryptCookies as BaseEncryptCookies;
Expand All @@ -10,6 +11,7 @@
use Illuminate\Http\Request;
use Inertia\Inertia;
use Symfony\Component\HttpFoundation\Response;
use Tighten\Ziggy\Ziggy;

return Application::configure(basePath: dirname(__DIR__))
->withRouting(
Expand All @@ -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.',
]);
Expand Down
1 change: 1 addition & 0 deletions resources/css/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 *));

Expand Down
33 changes: 31 additions & 2 deletions resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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, {
Expand All @@ -43,6 +71,7 @@ createInertiaApp({
},
},
},
pt: globalPt,
})
.use(ToastService)
.component('InertiaHead', Head)
Expand Down
1 change: 0 additions & 1 deletion resources/js/layouts/app/HeaderLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ const toggleMobileUserMenu = (event) => {
</div>
</template>
</Drawer>
<Toast position="top-center" />
</Teleport>
</ClientOnly>
<div class="min-h-screen">
Expand Down
1 change: 0 additions & 1 deletion resources/js/layouts/app/SidebarLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ const toggleMobileUserMenu = (event) => {
</div>
</template>
</Drawer>
<Toast position="top-center" />
</Teleport>
</ClientOnly>

Expand Down
29 changes: 10 additions & 19 deletions resources/js/pages/Error.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,17 @@ import { computed } from 'vue';
import { ArrowLeft } from 'lucide-vue-next';

const props = defineProps({
errorTitles: Object,
errorDetails: Object,
status: Number,
homepageRoute: String,
status: Number
});

const title = computed(() => {
return {
503: 'Service Unavailable',
500: 'Server Error',
404: 'Page Not Found',
403: 'Forbidden',
}[props.status];
return props.errorTitles[props.status];
});

const description = computed(() => {
return {
503: 'Sorry, we are doing some maintenance. Please check back soon.',
500: 'Whoops, something went wrong on our servers.',
404: 'Sorry, the page you are looking for could not be found.',
403: 'Sorry, you are forbidden from accessing this page.',
}[props.status];
const details = computed(() => {
return props.errorDetails[props.status];
});
</script>

Expand All @@ -33,15 +24,15 @@ const description = computed(() => {
<div class="h-screen flex items-center justify-center">
<Card class="p-4 py-6 sm:p-12">
<template #content>
<div class="flex flex-col gap-8 items-center justify-center text-center">
<h1 class="font-extrabold text-5xl md:text-8xl text-primary">
<div class="flex flex-col gap-6 md:gap-8 items-center justify-center text-center">
<h1 class="font-extrabold text-2xl md:text-4xl text-primary">
{{ props.status }}
</h1>
<h2 class="font-extrabold text-4xl md:text-6xl">
{{ title }}
</h2>
<p class="text-xl font-semibold md:text-3xl text-muted-color">
{{ description }}
<p class="text-xl font-semibold text-muted-color">
{{ details }}
</p>
<InertiaLink :href="props.homepageRoute">
<Button
Expand Down
13 changes: 12 additions & 1 deletion resources/js/ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { route as ziggyRoute } from 'ziggy-js';

import PrimeVue from 'primevue/config';
import ToastService from 'primevue/toastservice';
import Toast from 'primevue/toast';

import Container from '@/components/Container.vue';
import PageTitleSection from '@/components/PageTitleSection.vue';
Expand All @@ -33,8 +34,18 @@ createServer((page) =>
emitAuto: true,
});

// Global Toast component
const Root = {
setup() {
return () => h('div', [
h(App, props),
h(Toast, { position: 'bottom-right' })
]);
}
};

// Create app
const app = createSSRApp({ render: () => h(App, props) });
const app = createSSRApp(Root);

// Configure Ziggy for SSR
const ziggyConfig = {
Expand Down
15 changes: 15 additions & 0 deletions resources/js/theme/global-pt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Global pass through styling for components
* https://primevue.org/passthrough/#global
*/
export default {
toast: {
root: {
// Full width/centered on mobile, bottom right desktop
class: 'fixed! left-4! right-4! bottom-4! w-auto! md:right-8! md:bottom-8! sm:w-[25rem]! sm:not-fixed! sm:left-auto! sm:ml-auto!'
},
message: {
class: 'shadow-lg mb-0 mt-4'
},
},
};