While coding on security policies on a little project I've encountered the following issue: After denying a specific action for a user, I would like to show them in a nice and clean way, why they can't perform that given action. And except for that, (flash) notifications are a really nice way to...
While coding on security policies on a little project I've encountered the following issue: After denying a specific action for a user, I would like to show them in a nice and clean way, why they can't perform that given action. And except for that, (flash) notifications are a really nice way to inform your user about other stuff, like a successful model creation, etc.
To make sure the logged-in user can perform an action, I check that with policies in my controller.
1$this->authorize('view', $post);
If the user is unauthenticated or doesn't have the rights to perform the request it will throw a 403 error. Since I would like to override the behaviour of displaying such an error, I've modified the render
method in my App\Exceptions\Handler.php
file slightly:
1public function render($request, Throwable $e) 2{ 3 $response = parent::render($request, $e); 4 5 if($response->status() == 403) { 6 return redirect()->back()->with('notification', [ 7 'color' => 'red', 8 'title' => 'Error', 9 'message'=> $e->getMessage(),10 ]);11 }12 13 return $response;14}
When the error code is 403, it will redirect my user back to the last visited page, including flash data for my notification. Since I would like to use these notifications for other events too, it makes sense to pass a color or style variable here as well.
To pass the created flash data to the frontend, I've modified my App\Http\Middelware\HandleInertiaRequests.php
middleware. I've added a share
function to it, which passes the data to my $page.props
object.
1public function share(Request $request)2{3 return array_merge(parent::share($request), [4 'flash' => [5 'notification' => fn () => $request->session()->get('notification')6 ],7 ]);8}
To display that just created notification, I've created a Vue component called NotificationBox
.
It contains the template part:
1<template> 2 <div 3 class="fixed inset-0 z-50 flex items-end justify-center px-4 py-6 pointer-events-none sm:p-6 sm:items-start sm:justify-end"> 4 <transition 5 appear 6 enter-active-class="transform ease-out duration-300 transition" 7 enter-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2" 8 enter-to-class="translate-y-0 opacity-100 sm:translate-x-0" 9 leave-active-class="transition ease-in duration-100"10 leave-class="opacity-100"11 leave-to-class="opacity-0">12 <div13 v-show="notification && show"14 class="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden"15 @mouseleave="show = false">16 <div class="p-4">17 <div class="flex items-start">18 <div class="flex-shrink-0">19 <Icon :class="'text-' + notification.color + '-400'" :icon="icon"/>20 </div>21 <div class="ml-3 w-0 flex-1 pt-0.5">22 <template v-if="notification.title && notification.message">23 <p class="text-sm font-medium text-gray-900">24 {{ notification.title }}25 </p>26 <p class="mt-1 text-sm text-gray-500">27 {{ notification.message }}28 </p>29 </template>30 <template v-else>31 <p class="text-sm font-medium text-gray-900">32 {{ notification.message || notification.title }}33 </p>34 </template>35 </div>36 <div class="ml-4 flex-shrink-0 flex">37 <button38 class="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"39 @click="show = false">40 <span class="sr-only">Close</span>41 <icon icon="x" class="h-5 w-5"/>42 </button>43 </div>44 </div>45 </div>46 </div>47 </transition>48 </div>49</template>
Which uses a beautiful notification template from TailwindUI, as well as the logic to display a notification in the correct color, with the correct icon and with or without title. It also contains the Javascript part, which isn't that much:
1<script> 2import Icon from "@/Shared/Icon"; 3 4export default { 5 components: {Icon}, 6 data() { 7 return { 8 show: true, 9 }10 },11 12 computed: {13 notification() {14 return this.$page.props.flash.notification;15 },16 17 icon() {18 return {19 red: 'exclamation-circle',20 green: 'check-circle',21 }[this.notification.color];22 }23 },24}25</script>
I would like that my users can hide the notification and because of that I've created a show
property, which will hide the notification when set to false. For simplicity it also contains the notification as computed property, so I don't have to write this.$page.props.flash.notification
too often.
Flash notification are a very nice way to present information to your user, which should not override the full page. The given code creates an output like the following:
The user can hide the notification, and it will get automatically hidden after the users cursor leaves the notification.
Once in a while I send an email with some project updates, article drafts or other cool stuff. I won’t share your email and I won’t let your inbox overflow. Promised.