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\User;
|
||||
use App\Services\ActivityLogger;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
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));
|
||||
|
||||
try {
|
||||
$user = User::query()->updateOrCreate(
|
||||
['username' => $azureUser->getEmail()],
|
||||
[
|
||||
@@ -134,6 +136,11 @@ private function processCallback(): RedirectResponse
|
||||
auth()->login($user);
|
||||
|
||||
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('/');
|
||||
}
|
||||
|
||||
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 { usePage } from '@inertiajs/vue3'
|
||||
import PageHeader from '@/Components/PageHeader.vue'
|
||||
import FlashNotification from '@/Components/FlashNotification.vue'
|
||||
|
||||
const page = usePage()
|
||||
|
||||
@@ -13,6 +14,7 @@ const pageTitle = computed(() => {
|
||||
<template>
|
||||
<div class="min-h-screen flex flex-col">
|
||||
<PageHeader :title="pageTitle" />
|
||||
<FlashNotification />
|
||||
|
||||
<!-- Growth symbol watermark -->
|
||||
<img
|
||||
|
||||
Reference in New Issue
Block a user