Adds messages when stuff goes wrong
This commit is contained in:
BIN
.playwright-mcp/page-2026-03-19T11-19-05-308Z.png
Normal file
BIN
.playwright-mcp/page-2026-03-19T11-19-05-308Z.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
@@ -8,6 +8,7 @@
|
|||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\ActivityLogger;
|
use App\Services\ActivityLogger;
|
||||||
|
use Illuminate\Database\QueryException;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
@@ -100,6 +101,7 @@ private function processCallback(): RedirectResponse
|
|||||||
|
|
||||||
Log::info('[Azure SSO] Full Azure user dump', json_decode(json_encode($azureUser), true));
|
Log::info('[Azure SSO] Full Azure user dump', json_decode(json_encode($azureUser), true));
|
||||||
|
|
||||||
|
try {
|
||||||
$user = User::query()->updateOrCreate(
|
$user = User::query()->updateOrCreate(
|
||||||
['username' => $azureUser->getEmail()],
|
['username' => $azureUser->getEmail()],
|
||||||
[
|
[
|
||||||
@@ -134,6 +136,11 @@ private function processCallback(): RedirectResponse
|
|||||||
auth()->login($user);
|
auth()->login($user);
|
||||||
|
|
||||||
ActivityLogger::log('login', $user->id, metadata: ['email' => $user->email, 'firm_name' => Arr::get($azureUser->user, 'companyName')]);
|
ActivityLogger::log('login', $user->id, metadata: ['email' => $user->email, 'firm_name' => Arr::get($azureUser->user, 'companyName')]);
|
||||||
|
} catch (QueryException $e) {
|
||||||
|
Log::error('[Azure SSO] Database error during user upsert', ['message' => $e->getMessage(), 'email' => $azureUser->getEmail()]);
|
||||||
|
|
||||||
|
return redirect('/')->with('error', 'Something went wrong during sign-in. Please try again or contact support.');
|
||||||
|
}
|
||||||
|
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
}
|
}
|
||||||
|
|||||||
118
resources/js/Components/FlashNotification.vue
Normal file
118
resources/js/Components/FlashNotification.vue
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, watch, onUnmounted } from 'vue'
|
||||||
|
import { usePage } from '@inertiajs/vue3'
|
||||||
|
import { CheckCircleIcon, ExclamationTriangleIcon, XMarkIcon } from '@heroicons/vue/20/solid'
|
||||||
|
|
||||||
|
const page = usePage()
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const message = ref('')
|
||||||
|
const type = ref('success') // 'success' | 'error'
|
||||||
|
|
||||||
|
let autoDismissTimer = null
|
||||||
|
|
||||||
|
function clearTimer() {
|
||||||
|
if (autoDismissTimer) {
|
||||||
|
clearTimeout(autoDismissTimer)
|
||||||
|
autoDismissTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismiss() {
|
||||||
|
visible.value = false
|
||||||
|
clearTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
function showFlash(flash) {
|
||||||
|
clearTimer()
|
||||||
|
|
||||||
|
if (flash?.error) {
|
||||||
|
message.value = flash.error
|
||||||
|
type.value = 'error'
|
||||||
|
visible.value = true
|
||||||
|
// Error messages stay until manually dismissed
|
||||||
|
} else if (flash?.success) {
|
||||||
|
message.value = flash.success
|
||||||
|
type.value = 'success'
|
||||||
|
visible.value = true
|
||||||
|
// Auto-dismiss success after 8 seconds
|
||||||
|
autoDismissTimer = setTimeout(() => {
|
||||||
|
visible.value = false
|
||||||
|
}, 8000)
|
||||||
|
} else {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for flash prop changes on each Inertia page visit
|
||||||
|
watch(
|
||||||
|
() => page.props.flash,
|
||||||
|
(flash) => showFlash(flash),
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearTimer()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Transition
|
||||||
|
enter-active-class="transition duration-300 ease-out"
|
||||||
|
enter-from-class="-translate-y-full opacity-0"
|
||||||
|
enter-to-class="translate-y-0 opacity-100"
|
||||||
|
leave-active-class="transition duration-200 ease-in"
|
||||||
|
leave-from-class="translate-y-0 opacity-100"
|
||||||
|
leave-to-class="-translate-y-full opacity-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="visible"
|
||||||
|
:class="[
|
||||||
|
'relative z-40 w-full shadow-lg',
|
||||||
|
type === 'error'
|
||||||
|
? 'bg-rose-600'
|
||||||
|
: 'bg-emerald-600',
|
||||||
|
]"
|
||||||
|
role="alert"
|
||||||
|
:aria-live="type === 'error' ? 'assertive' : 'polite'"
|
||||||
|
>
|
||||||
|
<div class="px-6 py-3 flex items-center gap-3 max-w-7xl mx-auto">
|
||||||
|
<!-- Icon -->
|
||||||
|
<component
|
||||||
|
:is="type === 'error' ? ExclamationTriangleIcon : CheckCircleIcon"
|
||||||
|
class="w-5 h-5 text-white/90 shrink-0"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Message -->
|
||||||
|
<p class="flex-1 text-sm font-medium text-white leading-snug">
|
||||||
|
{{ message }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Dismiss button -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="ml-auto shrink-0 rounded p-1 text-white/70 hover:text-white hover:bg-white/10 transition-colors focus:outline-none focus:ring-2 focus:ring-white/40"
|
||||||
|
aria-label="Dismiss notification"
|
||||||
|
@click="dismiss"
|
||||||
|
>
|
||||||
|
<XMarkIcon class="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress bar for success auto-dismiss -->
|
||||||
|
<div
|
||||||
|
v-if="type === 'success'"
|
||||||
|
class="absolute bottom-0 left-0 h-[2px] bg-white/30 animate-[shrink_8s_linear_forwards]"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@keyframes shrink {
|
||||||
|
from { width: 100%; }
|
||||||
|
to { width: 0%; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { usePage } from '@inertiajs/vue3'
|
import { usePage } from '@inertiajs/vue3'
|
||||||
import PageHeader from '@/Components/PageHeader.vue'
|
import PageHeader from '@/Components/PageHeader.vue'
|
||||||
|
import FlashNotification from '@/Components/FlashNotification.vue'
|
||||||
|
|
||||||
const page = usePage()
|
const page = usePage()
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ const pageTitle = computed(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen flex flex-col">
|
<div class="min-h-screen flex flex-col">
|
||||||
<PageHeader :title="pageTitle" />
|
<PageHeader :title="pageTitle" />
|
||||||
|
<FlashNotification />
|
||||||
|
|
||||||
<!-- Growth symbol watermark -->
|
<!-- Growth symbol watermark -->
|
||||||
<img
|
<img
|
||||||
|
|||||||
Reference in New Issue
Block a user