mirror of
https://github.com/php-flasher/php-flasher.git
synced 2026-03-31 15:07:47 +01:00
1709 lines
74 KiB
HTML
1709 lines
74 KiB
HTML
---
|
|
permalink: /inertia/
|
|
title: Inertia
|
|
description: Discover how to integrate flash notifications into your Inertia.js application with PHPFlasher. Follow this guide to set up the library and enhance your user interface with dynamic messages.
|
|
---
|
|
|
|
<div class="max-w-7xl mx-auto">
|
|
<!-- Main Content with Styled Sections -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
|
<!-- Table of contents - Desktop -->
|
|
<div class="w-full bg-white border-b border-slate-100 shadow-sm">
|
|
<div class="px-6 py-4 flex items-center gap-4 overflow-x-auto no-scrollbar">
|
|
<a href="#installation" class="text-sm font-medium text-slate-700 hover:text-emerald-600 whitespace-nowrap transition-colors">
|
|
<i class="fa-solid fa-download mr-1.5 text-emerald-500"></i> Installation
|
|
</a>
|
|
<span class="text-slate-300">|</span>
|
|
<a href="#usage" class="text-sm font-medium text-slate-700 hover:text-emerald-600 whitespace-nowrap transition-colors">
|
|
<i class="fa-solid fa-code mr-1.5 text-emerald-500"></i> Usage
|
|
</a>
|
|
<span class="text-slate-300">|</span>
|
|
<a href="#examples" class="text-sm font-medium text-slate-700 hover:text-emerald-600 whitespace-nowrap transition-colors">
|
|
<i class="fa-solid fa-book mr-1.5 text-emerald-500"></i> Examples
|
|
</a>
|
|
<span class="text-slate-300">|</span>
|
|
<a href="#frontend" class="text-sm font-medium text-slate-700 hover:text-emerald-600 whitespace-nowrap transition-colors">
|
|
<i class="fa-solid fa-code-branch mr-1.5 text-emerald-500"></i> Frontend
|
|
</a>
|
|
<span class="text-slate-300">|</span>
|
|
<a href="#advanced" class="text-sm font-medium text-slate-700 hover:text-emerald-600 whitespace-nowrap transition-colors">
|
|
<i class="fa-solid fa-gear mr-1.5 text-emerald-500"></i> Advanced
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content area with custom styling -->
|
|
<div class="prose prose-slate max-w-none p-6 md:p-8">
|
|
<!-- Introduction Section -->
|
|
<div id="introduction" class="scroll-mt-20">
|
|
<p class="mb-4">
|
|
<strong><span class="text-indigo-900">PHP<span class="text-indigo-500">Flasher</span></span></strong>
|
|
works seamlessly with <span class="text-emerald-600 font-semibold">Inertia.js</span>, providing a smooth way to display flash notifications in your single-page applications.
|
|
</p>
|
|
|
|
<!-- Version requirements -->
|
|
<div class="mb-5">
|
|
<!-- Requirements Cards Container -->
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<!-- PHP Card -->
|
|
<div class="group relative bg-white rounded-lg border border-indigo-200 shadow-sm overflow-hidden transition-all duration-300 hover:shadow-md hover:-translate-y-1">
|
|
<!-- Subtle accent line -->
|
|
<div class="absolute top-0 inset-x-0 h-1 bg-gradient-to-r from-indigo-500 to-indigo-600"></div>
|
|
|
|
<div class="p-3">
|
|
<div class="flex items-center">
|
|
<div class="bg-gradient-to-br from-indigo-500 to-indigo-600 rounded-full h-10 w-10 flex items-center justify-center shadow-sm group-hover:shadow transition-all">
|
|
<i class="fa-brands fa-php text-white text-lg"></i>
|
|
</div>
|
|
|
|
<div class="ml-3">
|
|
<div class="text-xs uppercase tracking-wider text-indigo-500 font-semibold">Required</div>
|
|
<h4 class="font-bold text-slate-800">PHP Version</h4>
|
|
<div class="inline-block px-2 py-0.5 bg-indigo-100 text-indigo-800 text-xs font-medium rounded-full">
|
|
v8.2 or higher
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Laravel Card -->
|
|
<div class="group relative bg-white rounded-lg border border-red-200 shadow-sm overflow-hidden transition-all duration-300 hover:shadow-md hover:-translate-y-1">
|
|
<!-- Subtle accent line -->
|
|
<div class="absolute top-0 inset-x-0 h-1 bg-gradient-to-r from-red-600 to-red-700"></div>
|
|
|
|
<div class="p-3">
|
|
<div class="flex items-center">
|
|
<div class="bg-gradient-to-br from-red-600 to-red-700 rounded-full h-10 w-10 flex items-center justify-center shadow-sm group-hover:shadow transition-all">
|
|
<i class="fa-brands fa-laravel text-white text-lg"></i>
|
|
</div>
|
|
|
|
<div class="ml-3">
|
|
<div class="text-xs uppercase tracking-wider text-red-500 font-semibold">Required</div>
|
|
<h4 class="font-bold text-slate-800">Laravel Version</h4>
|
|
<div class="inline-block px-2 py-0.5 bg-red-50 text-red-700 text-xs font-medium rounded-full">
|
|
v11.0 or higher
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Inertia Card -->
|
|
<div class="group relative bg-white rounded-lg border border-emerald-200 shadow-sm overflow-hidden transition-all duration-300 hover:shadow-md hover:-translate-y-1">
|
|
<!-- Subtle accent line -->
|
|
<div class="absolute top-0 inset-x-0 h-1 bg-gradient-to-r from-emerald-500 to-emerald-600"></div>
|
|
|
|
<div class="p-3">
|
|
<div class="flex items-center">
|
|
<div class="bg-gradient-to-br from-emerald-500 to-emerald-600 rounded-full h-10 w-10 flex items-center justify-center shadow-sm group-hover:shadow transition-all">
|
|
<i class="fa-solid fa-code text-white text-lg"></i>
|
|
</div>
|
|
|
|
<div class="ml-3">
|
|
<div class="text-xs uppercase tracking-wider text-emerald-500 font-semibold">Required</div>
|
|
<h4 class="font-bold text-slate-800">Inertia.js</h4>
|
|
<div class="inline-block px-2 py-0.5 bg-emerald-50 text-emerald-700 text-xs font-medium rounded-full">
|
|
v1.0 or higher
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Legacy version note -->
|
|
<div class="bg-amber-50 border-l-4 border-amber-300 rounded-r-lg p-3 mt-3">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0 flex items-center justify-center">
|
|
<i class="fa-solid fa-lightbulb text-amber-500"></i>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h4 class="text-sm font-medium text-amber-800">Using older versions?</h4>
|
|
<p class="text-xs text-amber-700">
|
|
If you need to use PHP < v8.2, Laravel < v11.0, or an older Inertia.js version, use
|
|
<strong><span class="text-indigo-900">PHP<span class="text-indigo-500">Flasher</span></span> v1</strong> instead.
|
|
<a href="https://php-flasher.github.io/" class="text-amber-800 underline hover:text-amber-900">
|
|
Check out the v1 documentation here
|
|
</a>.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Installation Section -->
|
|
<div id="installation" class="mt-10 scroll-mt-20">
|
|
<h2 class="text-2xl font-bold text-slate-800 flex items-center gap-3 mb-4 border-b border-slate-100 pb-2">
|
|
<span class="p-1.5 bg-gradient-to-br from-emerald-50 to-emerald-100 text-emerald-600 rounded-lg shadow-sm">
|
|
<i class="fa-solid fa-download"></i>
|
|
</span>
|
|
Installation
|
|
</h2>
|
|
<p class="mb-4">
|
|
To use <strong><span class="text-indigo-900">PHP<span class="text-indigo-500">Flasher</span></span></strong> with Inertia.js,
|
|
you'll need to install both the PHP package and JavaScript package.
|
|
</p>
|
|
|
|
<h3 class="text-xl font-semibold text-slate-700 mt-6 mb-3">1. Install the PHP Package</h3>
|
|
|
|
<!-- Terminal code block -->
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-4">
|
|
<div class="bg-slate-800 p-4 flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<div class="flex space-x-1 mr-4">
|
|
<div class="w-3 h-3 rounded-full bg-red-400"></div>
|
|
<div class="w-3 h-3 rounded-full bg-yellow-400"></div>
|
|
<div class="w-3 h-3 rounded-full bg-green-400"></div>
|
|
</div>
|
|
<span class="text-slate-400 text-sm">Terminal</span>
|
|
</div>
|
|
<div class="text-slate-400 text-sm">PHP Installation</div>
|
|
</div>
|
|
|
|
<div class="p-6 bg-slate-900">
|
|
<div class="flex flex-col gap-4">
|
|
<pre class="bg-slate-800 p-4 rounded-lg text-sm overflow-x-auto"><code class="language-bash text-slate-200">composer require php-flasher/flasher-laravel</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 class="text-xl font-semibold text-slate-700 mt-6 mb-3">2. Add JavaScript Package</h3>
|
|
<p class="mb-4">Add <code class="bg-slate-100 px-2 py-1 rounded text-emerald-600">@flasher/flasher</code> to your <code class="bg-slate-100 px-2 py-1 rounded text-emerald-600">package.json</code>:</p>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-4">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">package.json</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-json">{
|
|
"dependencies": {
|
|
"@flasher/flasher": "file:vendor/php-flasher/flasher/Resources"
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="mb-4">Then, install the JavaScript dependencies:</p>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 p-4 flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<div class="flex space-x-1 mr-4">
|
|
<div class="w-3 h-3 rounded-full bg-red-400"></div>
|
|
<div class="w-3 h-3 rounded-full bg-yellow-400"></div>
|
|
<div class="w-3 h-3 rounded-full bg-green-400"></div>
|
|
</div>
|
|
<span class="text-slate-400 text-sm">Terminal</span>
|
|
</div>
|
|
<div class="text-slate-400 text-sm">JS Installation</div>
|
|
</div>
|
|
|
|
<div class="p-6 bg-slate-900">
|
|
<pre class="bg-slate-800 p-4 rounded-lg text-sm overflow-x-auto"><code class="language-bash text-slate-200">npm install --force</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Integration callout -->
|
|
<div class="bg-emerald-50 border-l-4 border-emerald-400 rounded-r-lg p-5 mb-6">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="fa-solid fa-lightbulb text-emerald-500"></i>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h4 class="text-sm font-medium text-emerald-800">Why the --force flag?</h4>
|
|
<p class="text-sm text-emerald-700 mt-1">
|
|
The <code class="bg-emerald-100 px-1.5 py-0.5 rounded text-emerald-600">--force</code> flag is required because we're installing a local package from the filesystem
|
|
rather than from npm. This is the recommended approach for PHPFlasher to ensure version compatibility.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Usage Section -->
|
|
<div id="usage" class="mt-10 scroll-mt-20">
|
|
<h2 class="text-2xl font-bold text-slate-800 flex items-center gap-3 mb-4 border-b border-slate-100 pb-2">
|
|
<span class="p-1.5 bg-gradient-to-br from-emerald-50 to-emerald-100 text-emerald-600 rounded-lg shadow-sm">
|
|
<i class="fa-solid fa-code"></i>
|
|
</span>
|
|
Usage
|
|
</h2>
|
|
|
|
<p class="mb-4">
|
|
To use <strong><span class="text-indigo-900">PHP<span class="text-indigo-500">Flasher</span></span></strong>
|
|
with Inertia.js, you need to:
|
|
</p>
|
|
|
|
<h3 class="text-xl font-semibold text-slate-700 mt-6 mb-3">1. Modify Your Middleware</h3>
|
|
<p class="mb-4">Update your <code class="bg-slate-100 px-2 py-1 rounded text-emerald-600">HandleInertiaRequests</code> middleware to share flash notifications:</p>
|
|
|
|
<!-- PHP Example with full file structure -->
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">app/Http/Middleware/HandleInertiaRequests.php</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-php"><?php
|
|
|
|
namespace App\Http\Middleware;
|
|
|
|
use Illuminate\Http\Request;
|
|
use Inertia\Middleware;
|
|
|
|
class HandleInertiaRequests extends Middleware
|
|
{
|
|
/**
|
|
* Defines the props that are shared by default.
|
|
*
|
|
* @see https://inertiajs.com/shared-data
|
|
*/
|
|
public function share(Request $request): array
|
|
{
|
|
return array_merge(parent::share($request), [
|
|
// Add flash messages to Inertia shared data
|
|
'messages' => flash()->render('array'),
|
|
|
|
// You can include other shared data here
|
|
'auth' => [
|
|
'user' => $request->user(),
|
|
],
|
|
]);
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pro tip box -->
|
|
<div class="bg-gradient-to-r from-emerald-50 to-teal-50 rounded-lg p-5 mb-6 border border-emerald-100">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="fa-solid fa-lightbulb text-amber-500 text-xl"></i>
|
|
</div>
|
|
<div class="ml-4">
|
|
<h4 class="text-sm font-medium text-emerald-800">Pro Tip: Multiple Ways to Share Flash Messages</h4>
|
|
<p class="text-sm text-emerald-700 mt-1">
|
|
The <code class="bg-emerald-100 px-1.5 py-0.5 rounded text-emerald-600">render('array')</code> method
|
|
converts flash messages to a format compatible with JavaScript. You can also use <code class="bg-emerald-100 px-1.5 py-0.5 rounded text-emerald-600">render('json')</code> and
|
|
parse it in your frontend code.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 class="text-xl font-semibold text-slate-700 mt-6 mb-3">2. Set Up Your Frontend</h3>
|
|
<p class="mb-4">Add the code to render notifications in your layout component (example shown with Vue.js):</p>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">resources/js/Shared/Layout.vue</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-html"><script>
|
|
import flasher from "@flasher/flasher";
|
|
|
|
export default {
|
|
props: {
|
|
messages: Object,
|
|
},
|
|
watch: {
|
|
messages(value) {
|
|
if (value) {
|
|
flasher.render(value);
|
|
}
|
|
}
|
|
},
|
|
mounted() {
|
|
// Display messages on initial load
|
|
if (this.messages) {
|
|
flasher.render(this.messages);
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<header class="bg-white shadow">
|
|
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
|
<!-- Your navigation -->
|
|
<h1 class="text-xl font-bold text-gray-900">My App</h1>
|
|
</div>
|
|
</header>
|
|
|
|
<main>
|
|
<div class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
|
<slot />
|
|
</div>
|
|
</main>
|
|
|
|
<footer class="bg-white border-t border-gray-200 mt-auto">
|
|
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
|
<p class="text-center text-gray-500 text-sm">
|
|
&copy; 2025 My Application
|
|
</p>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
</template></code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 class="text-xl font-semibold text-slate-700 mt-8 mb-3">3. Creating Flash Messages</h3>
|
|
<p class="mb-4">Now you can create flash notifications in your controllers:</p>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">app/Http/Controllers/UsersController.php</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-php"><?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Redirect;
|
|
use Inertia\Inertia;
|
|
|
|
class UsersController extends Controller
|
|
{
|
|
/**
|
|
* Display a listing of users.
|
|
*/
|
|
public function index()
|
|
{
|
|
return Inertia::render('Users/Index', [
|
|
'users' => User::all()
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Show the form for creating a new user.
|
|
*/
|
|
public function create()
|
|
{
|
|
return Inertia::render('Users/Create');
|
|
}
|
|
|
|
/**
|
|
* Store a newly created user in storage.
|
|
*/
|
|
public function store(Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'name' => 'required|string|max:255',
|
|
'email' => 'required|string|email|max:255|unique:users',
|
|
'password' => 'required|string|min:8|confirmed',
|
|
]);
|
|
|
|
User::create([
|
|
'name' => $validated['name'],
|
|
'email' => $validated['email'],
|
|
'password' => Hash::make($validated['password']),
|
|
]);
|
|
|
|
// Add a success notification
|
|
flash()->success('User created successfully!');
|
|
|
|
// Redirect back, and Inertia will handle passing the flash message
|
|
return Redirect::route('users.index');
|
|
}
|
|
|
|
/**
|
|
* Display the specified user.
|
|
*/
|
|
public function show(User $user)
|
|
{
|
|
return Inertia::render('Users/Show', [
|
|
'user' => $user
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Show the form for editing the specified user.
|
|
*/
|
|
public function edit(User $user)
|
|
{
|
|
return Inertia::render('Users/Edit', [
|
|
'user' => $user
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Update the specified user in storage.
|
|
*/
|
|
public function update(Request $request, User $user)
|
|
{
|
|
$validated = $request->validate([
|
|
'name' => 'required|string|max:255',
|
|
'email' => 'required|string|email|max:255|unique:users,email,' . $user->id,
|
|
]);
|
|
|
|
$user->update($validated);
|
|
|
|
flash()->success('User updated successfully!');
|
|
|
|
return Redirect::route('users.index');
|
|
}
|
|
|
|
/**
|
|
* Remove the specified user from storage.
|
|
*/
|
|
public function destroy(User $user)
|
|
{
|
|
$user->delete();
|
|
|
|
flash()->info('User has been deleted.');
|
|
|
|
return Redirect::route('users.index');
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notification types showcase -->
|
|
<div class="my-6">
|
|
<div class="bg-white rounded-lg p-4 border border-slate-200 shadow-sm mb-4">
|
|
<div class="flex items-center mb-2">
|
|
<div class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center mr-3">
|
|
<i class="fa-solid fa-circle-check text-green-500"></i>
|
|
</div>
|
|
<h3 class="font-medium text-slate-800">Success Message</h3>
|
|
</div>
|
|
<pre class="bg-slate-50 rounded-lg p-3 text-sm overflow-x-auto mt-2"><code class="language-php">flash()->success('Item created successfully!')</code></pre>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg p-4 border border-slate-200 shadow-sm mb-4">
|
|
<div class="flex items-center mb-2">
|
|
<div class="w-8 h-8 rounded-full bg-red-100 flex items-center justify-center mr-3">
|
|
<i class="fa-solid fa-circle-xmark text-red-500"></i>
|
|
</div>
|
|
<h3 class="font-medium text-slate-800">Error Message</h3>
|
|
</div>
|
|
<pre class="bg-slate-50 rounded-lg p-3 text-sm overflow-x-auto mt-2"><code class="language-php">flash()->error('An error occurred!')</code></pre>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg p-4 border border-slate-200 shadow-sm mb-4">
|
|
<div class="flex items-center mb-2">
|
|
<div class="w-8 h-8 rounded-full bg-yellow-100 flex items-center justify-center mr-3">
|
|
<i class="fa-solid fa-triangle-exclamation text-yellow-500"></i>
|
|
</div>
|
|
<h3 class="font-medium text-slate-800">Warning Message</h3>
|
|
</div>
|
|
<pre class="bg-slate-50 rounded-lg p-3 text-sm overflow-x-auto mt-2"><code class="language-php">flash()->warning('Please review your submission.')</code></pre>
|
|
</div>
|
|
|
|
<div class="bg-white rounded-lg p-4 border border-slate-200 shadow-sm">
|
|
<div class="flex items-center mb-2">
|
|
<div class="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center mr-3">
|
|
<i class="fa-solid fa-circle-info text-blue-500"></i>
|
|
</div>
|
|
<h3 class="font-medium text-slate-800">Info Message</h3>
|
|
</div>
|
|
<pre class="bg-slate-50 rounded-lg p-3 text-sm overflow-x-auto mt-2"><code class="language-php">flash()->info('Your session expires in 10 minutes.')</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Expert tips -->
|
|
<div class="bg-slate-50 border border-slate-200 rounded-xl p-6 mb-6">
|
|
<div class="flex items-start">
|
|
<div class="flex-shrink-0 p-2 bg-emerald-100 rounded-full">
|
|
<i class="fa-solid fa-lightbulb text-emerald-600"></i>
|
|
</div>
|
|
<div class="ml-4">
|
|
<h4 class="text-lg font-semibold text-slate-800">Expert Tips for Inertia.js Integration</h4>
|
|
<ul class="mt-2 space-y-2 text-slate-600">
|
|
<li class="flex items-start">
|
|
<i class="fa-solid fa-check text-emerald-500 mr-2 mt-1"></i>
|
|
<span><strong>Keep It Consistent:</strong> Use the same notification types for similar actions throughout your application for a predictable user experience.</span>
|
|
</li>
|
|
<li class="flex items-start">
|
|
<i class="fa-solid fa-check text-emerald-500 mr-2 mt-1"></i>
|
|
<span><strong>Form Validation:</strong> Use flash notifications for general form status, but rely on Inertia's built-in error handling for field-specific validation errors.</span>
|
|
</li>
|
|
<li class="flex items-start">
|
|
<i class="fa-solid fa-check text-emerald-500 mr-2 mt-1"></i>
|
|
<span><strong>Watch for Changes:</strong> Always use the <code class="bg-slate-100 px-1.5 py-0.5 rounded text-emerald-600">watch</code> property to detect new notifications after navigation or form submissions.</span>
|
|
</li>
|
|
<li class="flex items-start">
|
|
<i class="fa-solid fa-check text-emerald-500 mr-2 mt-1"></i>
|
|
<span><strong>Package Bundling:</strong> When using Vite or Webpack, ensure @flasher/flasher is properly included in your bundle.</span>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Examples Section -->
|
|
<div id="examples" class="mt-10 scroll-mt-20">
|
|
<h2 class="text-2xl font-bold text-slate-800 flex items-center gap-3 mb-4 border-b border-slate-100 pb-2">
|
|
<span class="p-1.5 bg-gradient-to-br from-emerald-50 to-emerald-100 text-emerald-600 rounded-lg shadow-sm">
|
|
<i class="fa-solid fa-book"></i>
|
|
</span>
|
|
Examples
|
|
</h2>
|
|
|
|
<h3 class="text-xl font-semibold text-slate-700 mt-6 mb-3">CRUD Operations</h3>
|
|
<p class="mb-4">Here's a complete example of CRUD operations with flash messages:</p>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">app/Http/Controllers/ProductController.php</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-php"><?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Product;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Redirect;
|
|
use Inertia\Inertia;
|
|
|
|
class ProductController extends Controller
|
|
{
|
|
/**
|
|
* Display a listing of products.
|
|
*/
|
|
public function index()
|
|
{
|
|
return Inertia::render('Products/Index', [
|
|
'products' => Product::all()
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Show the form for creating a new product.
|
|
*/
|
|
public function create()
|
|
{
|
|
return Inertia::render('Products/Create');
|
|
}
|
|
|
|
/**
|
|
* Store a newly created product in storage.
|
|
*/
|
|
public function store(Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'name' => 'required|string|max:255',
|
|
'price' => 'required|numeric',
|
|
'description' => 'nullable|string|max:1000',
|
|
'category_id' => 'required|exists:categories,id',
|
|
]);
|
|
|
|
Product::create($validated);
|
|
|
|
flash()->success('Product created successfully!');
|
|
|
|
return Redirect::route('products.index');
|
|
}
|
|
|
|
/**
|
|
* Display the specified product.
|
|
*/
|
|
public function show(Product $product)
|
|
{
|
|
return Inertia::render('Products/Show', [
|
|
'product' => $product->load('category')
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Show the form for editing the specified product.
|
|
*/
|
|
public function edit(Product $product)
|
|
{
|
|
return Inertia::render('Products/Edit', [
|
|
'product' => $product,
|
|
'categories' => \App\Models\Category::all()
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Update the specified product in storage.
|
|
*/
|
|
public function update(Request $request, Product $product)
|
|
{
|
|
$validated = $request->validate([
|
|
'name' => 'required|string|max:255',
|
|
'price' => 'required|numeric',
|
|
'description' => 'nullable|string|max:1000',
|
|
'category_id' => 'required|exists:categories,id',
|
|
]);
|
|
|
|
$product->update($validated);
|
|
|
|
flash()->success('Product updated successfully!');
|
|
|
|
return Redirect::route('products.index');
|
|
}
|
|
|
|
/**
|
|
* Remove the specified product from storage.
|
|
*/
|
|
public function destroy(Product $product)
|
|
{
|
|
try {
|
|
$product->delete();
|
|
flash()->info('Product has been deleted.');
|
|
} catch (\Exception $e) {
|
|
flash()->error('Cannot delete this product. It may be in use.');
|
|
}
|
|
|
|
return Redirect::route('products.index');
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Best practice box -->
|
|
<div class="bg-green-50 border-l-4 border-green-400 rounded-r-lg p-5 mb-6">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="fa-solid fa-check-circle text-green-500"></i>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h4 class="text-sm font-medium text-green-800">Best Practice: Error Handling</h4>
|
|
<p class="text-sm text-green-700 mt-1">
|
|
Always wrap database operations in try-catch blocks and provide user-friendly error messages.
|
|
This helps users understand what went wrong and what actions they can take to resolve issues.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 class="text-xl font-semibold text-slate-700 mt-8 mb-3">Authentication Flow</h3>
|
|
<p class="mb-4">Here's an example of using flash notifications in an authentication flow:</p>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">app/Http/Controllers/Auth/AuthController.php</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-php"><?php
|
|
|
|
namespace App\Http\Controllers\Auth;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\Redirect;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Inertia\Inertia;
|
|
|
|
class AuthController extends Controller
|
|
{
|
|
/**
|
|
* Show the login form
|
|
*/
|
|
public function showLogin()
|
|
{
|
|
return Inertia::render('Auth/Login');
|
|
}
|
|
|
|
/**
|
|
* Handle user login request
|
|
*/
|
|
public function login(Request $request)
|
|
{
|
|
$credentials = $request->validate([
|
|
'email' => 'required|email',
|
|
'password' => 'required',
|
|
]);
|
|
|
|
if (Auth::attempt($credentials, $request->boolean('remember'))) {
|
|
$request->session()->regenerate();
|
|
|
|
flash()->success('Welcome back, ' . Auth::user()->name . '!');
|
|
|
|
return Redirect::intended(route('dashboard'));
|
|
}
|
|
|
|
throw ValidationException::withMessages([
|
|
'email' => __('auth.failed'),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Show registration form
|
|
*/
|
|
public function showRegistration()
|
|
{
|
|
return Inertia::render('Auth/Register');
|
|
}
|
|
|
|
/**
|
|
* Handle user registration
|
|
*/
|
|
public function register(Request $request)
|
|
{
|
|
$validated = $request->validate([
|
|
'name' => 'required|string|max:255',
|
|
'email' => 'required|string|email|max:255|unique:users',
|
|
'password' => 'required|string|min:8|confirmed',
|
|
]);
|
|
|
|
$user = User::create([
|
|
'name' => $validated['name'],
|
|
'email' => $validated['email'],
|
|
'password' => Hash::make($validated['password']),
|
|
]);
|
|
|
|
Auth::login($user);
|
|
|
|
flash()->success('Welcome to our application! Your account has been created.');
|
|
|
|
return Redirect::route('dashboard');
|
|
}
|
|
|
|
/**
|
|
* Log the user out
|
|
*/
|
|
public function logout(Request $request)
|
|
{
|
|
Auth::logout();
|
|
|
|
$request->session()->invalidate();
|
|
$request->session()->regenerateToken();
|
|
|
|
flash()->info('You have been logged out successfully.');
|
|
|
|
return Redirect::route('login');
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 class="text-xl font-semibold text-slate-700 mt-8 mb-3">Form Validation</h3>
|
|
<p class="mb-4">Combine PHPFlasher with Inertia's validation error handling:</p>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">app/Http/Controllers/FormController.php</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-php"><?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Redirect;
|
|
use Inertia\Inertia;
|
|
|
|
class FormController extends Controller
|
|
{
|
|
/**
|
|
* Show the contact form
|
|
*/
|
|
public function showContactForm()
|
|
{
|
|
return Inertia::render('Contact/Form');
|
|
}
|
|
|
|
/**
|
|
* Handle the contact form submission
|
|
*/
|
|
public function submitContactForm(Request $request)
|
|
{
|
|
try {
|
|
$validated = $request->validate([
|
|
'name' => 'required|string|max:255',
|
|
'email' => 'required|email',
|
|
'message' => 'required|string|min:10|max:1000',
|
|
]);
|
|
|
|
// Process the form data
|
|
// e.g., send an email, save to database, etc.
|
|
|
|
// Show success notification
|
|
flash()->success(
|
|
'Thank you for contacting us! We will get back to you soon.',
|
|
'Message Sent'
|
|
);
|
|
|
|
return Redirect::route('contact.thankyou');
|
|
|
|
} catch (\Illuminate\Validation\ValidationException $e) {
|
|
// With Inertia, validation errors are automatically passed back
|
|
// Add a generic error message as well
|
|
flash()->error('Please fix the errors in your form.', 'Form Validation Error');
|
|
|
|
// With Inertia, this will redirect back with errors
|
|
throw $e;
|
|
} catch (\Exception $e) {
|
|
// Handle other exceptions
|
|
flash()->error(
|
|
'Sorry, we could not process your request. Please try again later.',
|
|
'System Error'
|
|
);
|
|
|
|
return Redirect::back();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show thank you page
|
|
*/
|
|
public function showThankYou()
|
|
{
|
|
return Inertia::render('Contact/ThankYou');
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Vue.js form example -->
|
|
<h3 class="text-xl font-semibold text-slate-700 mt-6 mb-3">Corresponding Vue Form Component</h3>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">resources/js/Pages/Contact/Form.vue</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-html"><script setup>
|
|
import { useForm } from '@inertiajs/vue3'
|
|
import Layout from '@/Layouts/MainLayout.vue'
|
|
|
|
const form = useForm({
|
|
name: '',
|
|
email: '',
|
|
message: ''
|
|
})
|
|
|
|
const submit = () => {
|
|
form.post(route('contact.submit'))
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<Layout>
|
|
<div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
|
|
<h1 class="text-2xl font-semibold text-gray-900 mb-6">Contact Us</h1>
|
|
|
|
<form @submit.prevent="submit" class="space-y-6">
|
|
<div>
|
|
<label for="name" class="block text-sm font-medium text-gray-700">Name</label>
|
|
<input
|
|
id="name"
|
|
v-model="form.name"
|
|
type="text"
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200 focus:ring-opacity-50"
|
|
/>
|
|
<div v-if="form.errors.name" class="text-red-500 text-sm mt-1">{{ form.errors.name }}</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="email" class="block text-sm font-medium text-gray-700">Email</label>
|
|
<input
|
|
id="email"
|
|
v-model="form.email"
|
|
type="email"
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200 focus:ring-opacity-50"
|
|
/>
|
|
<div v-if="form.errors.email" class="text-red-500 text-sm mt-1">{{ form.errors.email }}</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="message" class="block text-sm font-medium text-gray-700">Message</label>
|
|
<textarea
|
|
id="message"
|
|
v-model="form.message"
|
|
rows="4"
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200 focus:ring-opacity-50"
|
|
></textarea>
|
|
<div v-if="form.errors.message" class="text-red-500 text-sm mt-1">{{ form.errors.message }}</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end">
|
|
<button
|
|
type="submit"
|
|
:disabled="form.processing"
|
|
class="inline-flex items-center px-4 py-2 bg-emerald-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-emerald-700 active:bg-emerald-800 focus:outline-none focus:border-emerald-800 focus:ring focus:ring-emerald-300 disabled:opacity-25 transition"
|
|
>
|
|
<span v-if="form.processing">Processing...</span>
|
|
<span v-else>Submit</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</Layout>
|
|
</template></code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-emerald-50 border-l-4 border-emerald-400 rounded-r-lg p-5 mb-6">
|
|
<div class="flex items-center">
|
|
<div class="flex-shrink-0">
|
|
<i class="fa-solid fa-lightbulb text-emerald-500"></i>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h4 class="text-sm font-medium text-emerald-800">Tip: Handling Both Generic and Field-Specific Errors</h4>
|
|
<p class="text-sm text-emerald-700 mt-1">
|
|
As shown in the example above, it's often helpful to use PHPFlasher for global form status messages
|
|
while using Inertia's built-in error handling for field-specific validation errors.
|
|
This gives users both context-specific and general feedback.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Frontend Framework Section with Tabs -->
|
|
<div id="frontend" class="mt-10 scroll-mt-20">
|
|
<h2 class="text-2xl font-bold text-slate-800 flex items-center gap-3 mb-4 border-b border-slate-100 pb-2">
|
|
<span class="p-1.5 bg-gradient-to-br from-emerald-50 to-emerald-100 text-emerald-600 rounded-lg shadow-sm">
|
|
<i class="fa-solid fa-code-branch"></i>
|
|
</span>
|
|
Frontend Framework Integration
|
|
</h2>
|
|
|
|
<p class="mb-4">
|
|
Inertia.js works with multiple frontend frameworks. Here's how to integrate PHPFlasher with each:
|
|
</p>
|
|
|
|
<!-- Framework tabs -->
|
|
<div class="mb-6">
|
|
<div class="border-b border-gray-200">
|
|
<nav class="flex -mb-px">
|
|
<button
|
|
id="vueTab"
|
|
onclick="switchTab('vue')"
|
|
class="px-4 py-2 font-medium text-sm border-b-2 border-transparent hover:border-emerald-500 hover:text-emerald-600 transition-colors"
|
|
>
|
|
<i class="fa-brands fa-vuejs mr-1.5 text-emerald-500"></i>
|
|
Vue.js
|
|
</button>
|
|
<button
|
|
id="reactTab"
|
|
onclick="switchTab('react')"
|
|
class="px-4 py-2 font-medium text-sm border-b-2 border-transparent hover:border-emerald-500 hover:text-emerald-600 transition-colors"
|
|
>
|
|
<i class="fa-brands fa-react mr-1.5 text-emerald-500"></i>
|
|
React
|
|
</button>
|
|
<button
|
|
id="svelteTab"
|
|
onclick="switchTab('svelte')"
|
|
class="px-4 py-2 font-medium text-sm border-b-2 border-transparent hover:border-emerald-500 hover:text-emerald-600 transition-colors"
|
|
>
|
|
<i class="fa-solid fa-code mr-1.5 text-emerald-500"></i>
|
|
Svelte
|
|
</button>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Vue.js content -->
|
|
<div id="vueContent" class="mt-4">
|
|
<h3 class="text-xl font-semibold text-slate-700 mb-3">Vue.js Integration</h3>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">resources/js/Layouts/AppLayout.vue</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-html"><script setup>
|
|
import { computed, watch } from 'vue'
|
|
import { usePage } from '@inertiajs/vue3'
|
|
import flasher from '@flasher/flasher'
|
|
|
|
// Access shared data from Inertia
|
|
const page = usePage()
|
|
const messages = computed(() => page.props.messages)
|
|
|
|
// Watch for changes in flash messages
|
|
watch(
|
|
messages,
|
|
(newMessages) => {
|
|
if (newMessages) {
|
|
flasher.render(newMessages)
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<div class="min-h-screen bg-gray-100">
|
|
<nav class="bg-white border-b border-gray-100">
|
|
<!-- Navigation content -->
|
|
</nav>
|
|
|
|
<!-- Page Content -->
|
|
<main>
|
|
<slot />
|
|
</main>
|
|
|
|
<footer class="bg-white border-t border-gray-100 mt-auto">
|
|
<!-- Footer content -->
|
|
</footer>
|
|
</div>
|
|
</template></code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="mb-4">Using flash messages in a Vue component:</p>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">resources/js/Pages/Dashboard.vue</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-html"><script setup>
|
|
import { useForm } from '@inertiajs/vue3'
|
|
import AppLayout from '@/Layouts/AppLayout.vue'
|
|
import flasher from '@flasher/flasher'
|
|
|
|
const form = useForm({
|
|
title: '',
|
|
content: ''
|
|
})
|
|
|
|
const submit = () => {
|
|
form.post(route('posts.store'), {
|
|
onSuccess: () => {
|
|
// You can also trigger notifications directly from the frontend
|
|
flasher.success('Post created from the frontend!')
|
|
|
|
// Reset the form
|
|
form.reset()
|
|
}
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<AppLayout>
|
|
<div class="py-12">
|
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
|
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
|
<div class="p-6 bg-white border-b border-gray-200">
|
|
<form @submit.prevent="submit">
|
|
<!-- Form fields -->
|
|
<button type="submit" :disabled="form.processing">Create Post</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AppLayout>
|
|
</template></code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- React content -->
|
|
<div id="reactContent" class="mt-4 hidden">
|
|
<h3 class="text-xl font-semibold text-slate-700 mb-3">React Integration</h3>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">resources/js/Layouts/AppLayout.jsx</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-jsx">import { useEffect } from 'react'
|
|
import { usePage } from '@inertiajs/react'
|
|
import flasher from '@flasher/flasher'
|
|
|
|
export default function AppLayout({ children }) {
|
|
const { messages } = usePage().props
|
|
|
|
// Render flash messages whenever they change
|
|
useEffect(() => {
|
|
if (messages) {
|
|
flasher.render(messages)
|
|
}
|
|
}, [messages])
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-100">
|
|
<nav className="bg-white border-b border-gray-100">
|
|
{/* Navigation content */}
|
|
</nav>
|
|
|
|
{/* Page Content */}
|
|
<main>{children}</main>
|
|
|
|
<footer className="bg-white border-t border-gray-100 mt-auto">
|
|
{/* Footer content */}
|
|
</footer>
|
|
</div>
|
|
)
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="mb-4">Using flash messages in a React component:</p>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">resources/js/Pages/Dashboard.jsx</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-jsx">import { useState } from 'react'
|
|
import { useForm } from '@inertiajs/react'
|
|
import AppLayout from '@/Layouts/AppLayout'
|
|
import flasher from '@flasher/flasher'
|
|
|
|
export default function Dashboard() {
|
|
const { data, setData, post, processing, errors, reset } = useForm({
|
|
title: '',
|
|
content: ''
|
|
})
|
|
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault()
|
|
|
|
post(route('posts.store'), {
|
|
onSuccess: () => {
|
|
// You can also trigger notifications directly from the frontend
|
|
flasher.success('Post created from the frontend!')
|
|
|
|
// Reset the form
|
|
reset()
|
|
}
|
|
})
|
|
}
|
|
|
|
return (
|
|
<AppLayout>
|
|
<div className="py-12">
|
|
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
|
<div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
|
<div className="p-6 bg-white border-b border-gray-200">
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="mb-4">
|
|
<label htmlFor="title" className="block text-sm font-medium text-gray-700">Title</label>
|
|
<input
|
|
type="text"
|
|
id="title"
|
|
value={data.title}
|
|
onChange={e => setData('title', e.target.value)}
|
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200"
|
|
/>
|
|
{errors.title && <div className="text-red-500 text-sm mt-1">{errors.title}</div>}
|
|
</div>
|
|
|
|
<div className="mb-4">
|
|
<label htmlFor="content" className="block text-sm font-medium text-gray-700">Content</label>
|
|
<textarea
|
|
id="content"
|
|
value={data.content}
|
|
onChange={e => setData('content', e.target.value)}
|
|
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200"
|
|
rows="4"
|
|
></textarea>
|
|
{errors.content && <div className="text-red-500 text-sm mt-1">{errors.content}</div>}
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={processing}
|
|
className="bg-emerald-600 text-white px-4 py-2 rounded-md hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500"
|
|
>
|
|
{processing ? 'Creating...' : 'Create Post'}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AppLayout>
|
|
)
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Svelte content -->
|
|
<div id="svelteContent" class="mt-4 hidden">
|
|
<h3 class="text-xl font-semibold text-slate-700 mb-3">Svelte Integration</h3>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">resources/js/Layouts/AppLayout.svelte</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-html"><script>
|
|
import { page } from '$inertia/inertia-svelte'
|
|
import { onMount, afterUpdate } from 'svelte'
|
|
import flasher from '@flasher/flasher'
|
|
|
|
export let title = ''
|
|
|
|
// Render flash messages initially
|
|
onMount(() => {
|
|
if ($page.props.messages) {
|
|
flasher.render($page.props.messages)
|
|
}
|
|
})
|
|
|
|
// Watch for changes in flash messages
|
|
afterUpdate(() => {
|
|
if ($page.props.messages) {
|
|
flasher.render($page.props.messages)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<div class="min-h-screen bg-gray-100">
|
|
<nav class="bg-white border-b border-gray-100">
|
|
<!-- Navigation content -->
|
|
</nav>
|
|
|
|
<!-- Page Content -->
|
|
<main>
|
|
<slot />
|
|
</main>
|
|
|
|
<footer class="bg-white border-t border-gray-100 mt-auto">
|
|
<!-- Footer content -->
|
|
</footer>
|
|
</div></code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="mb-4">Using flash messages in a Svelte component:</p>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">resources/js/Pages/Dashboard.svelte</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-html"><script>
|
|
import { inertia, useForm } from '@inertiajs/inertia-svelte'
|
|
import AppLayout from '@/Layouts/AppLayout.svelte'
|
|
import flasher from '@flasher/flasher'
|
|
|
|
let form = useForm({
|
|
title: '',
|
|
content: ''
|
|
})
|
|
|
|
function submit() {
|
|
$form.post(route('posts.store'), {
|
|
onSuccess: () => {
|
|
// You can also trigger notifications directly from the frontend
|
|
flasher.success('Post created from the frontend!')
|
|
|
|
// Reset the form
|
|
$form.reset()
|
|
}
|
|
})
|
|
}
|
|
</script>
|
|
|
|
<AppLayout>
|
|
<div class="py-12">
|
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
|
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
|
|
<div class="p-6 bg-white border-b border-gray-200">
|
|
<form on:submit|preventDefault={submit}>
|
|
<div class="mb-4">
|
|
<label for="title" class="block text-sm font-medium text-gray-700">Title</label>
|
|
<input
|
|
type="text"
|
|
id="title"
|
|
bind:value={$form.title}
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200"
|
|
/>
|
|
{#if $form.errors.title}
|
|
<div class="text-red-500 text-sm mt-1">{$form.errors.title}</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="mb-4">
|
|
<label for="content" class="block text-sm font-medium text-gray-700">Content</label>
|
|
<textarea
|
|
id="content"
|
|
bind:value={$form.content}
|
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-emerald-500 focus:ring focus:ring-emerald-200"
|
|
rows="4"
|
|
></textarea>
|
|
{#if $form.errors.content}
|
|
<div class="text-red-500 text-sm mt-1">{$form.errors.content}</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={$form.processing}
|
|
class="bg-emerald-600 text-white px-4 py-2 rounded-md hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500"
|
|
>
|
|
{$form.processing ? 'Creating...' : 'Create Post'}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AppLayout></code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Framework-specific tips -->
|
|
<div class="bg-white rounded-lg p-6 border border-slate-200 shadow-sm mb-8">
|
|
<h4 class="text-lg font-semibold text-slate-800 mb-3">Framework-Specific Integration Tips</h4>
|
|
|
|
<div class="space-y-4">
|
|
<div class="flex items-start">
|
|
<div class="flex-shrink-0 pt-1">
|
|
<i class="fa-brands fa-vuejs text-emerald-500 text-lg"></i>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h4 class="font-medium text-slate-800">Vue.js</h4>
|
|
<p class="text-slate-600 text-sm">Use the <code class="bg-slate-100 px-1.5 py-0.5 rounded">watch</code> function with <code class="bg-slate-100 px-1.5 py-0.5 rounded">immediate: true</code> to ensure flash messages are shown on both initial load and after navigation.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-start">
|
|
<div class="flex-shrink-0 pt-1">
|
|
<i class="fa-brands fa-react text-emerald-500 text-lg"></i>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h4 class="font-medium text-slate-800">React</h4>
|
|
<p class="text-slate-600 text-sm">With React, use <code class="bg-slate-100 px-1.5 py-0.5 rounded">useEffect</code> with <code class="bg-slate-100 px-1.5 py-0.5 rounded">messages</code> as a dependency to catch updates to flash messages.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-start">
|
|
<div class="flex-shrink-0 pt-1">
|
|
<i class="fa-solid fa-code text-emerald-500 text-lg"></i>
|
|
</div>
|
|
<div class="ml-3">
|
|
<h4 class="font-medium text-slate-800">Svelte</h4>
|
|
<p class="text-slate-600 text-sm">Combine <code class="bg-slate-100 px-1.5 py-0.5 rounded">onMount</code> and <code class="bg-slate-100 px-1.5 py-0.5 rounded">afterUpdate</code> lifecycle functions to ensure flash messages are shown both initially and after updates.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Advanced Section -->
|
|
<div id="advanced" class="mt-10 scroll-mt-20">
|
|
<h2 class="text-2xl font-bold text-slate-800 flex items-center gap-3 mb-4 border-b border-slate-100 pb-2">
|
|
<span class="p-1.5 bg-gradient-to-br from-emerald-50 to-emerald-100 text-emerald-600 rounded-lg shadow-sm">
|
|
<i class="fa-solid fa-gear"></i>
|
|
</span>
|
|
Advanced
|
|
</h2>
|
|
|
|
<h3 class="text-xl font-semibold text-slate-700 mt-6 mb-3">Custom Notification Options</h3>
|
|
<p class="mb-4">Customize your notifications with various options:</p>
|
|
|
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-slate-100 mb-6">
|
|
<div class="bg-slate-800 px-4 py-3 flex items-center">
|
|
<div class="flex space-x-1.5 mr-3">
|
|
<div class="w-2.5 h-2.5 rounded-full bg-red-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-yellow-500"></div>
|
|
<div class="w-2.5 h-2.5 rounded-full bg-green-500"></div>
|
|
</div>
|
|
<div class="text-white opacity-80 text-xs font-medium">CustomOptions.php</div>
|
|
</div>
|
|
<div>
|
|
<pre class="bg-slate-50 rounded-lg p-4 text-sm overflow-x-auto"><code class="language-php"><?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use Illuminate\Http\Request;
|
|
use Inertia\Inertia;
|
|
|
|
class NotificationController extends Controller
|
|
{
|
|
public function showExamples()
|
|
{
|
|
return Inertia::render('Notifications/Examples');
|
|
}
|
|
|
|
public function positionExample()
|
|
{
|
|
// Position options
|
|
flash()
|
|
->option('position', 'bottom-right')
|
|
->success('Positioned at the bottom-right');
|
|
|
|
return back();
|
|
}
|
|
|
|
public function timeoutExample()
|
|
{
|
|
// Timeout
|
|
flash()
|
|
->option('timeout', 8000) // 8 seconds
|
|
->info('This message stays longer');
|
|
|
|
return back();
|
|
}
|
|
|
|
public function animationExample()
|
|
{
|
|
// Animation
|
|
flash()
|
|
->option('showAnimation', 'fadeIn')
|
|
->option('hideAnimation', 'fadeOut')
|
|
->success('Custom animations');
|
|
|
|
return back();
|
|
}
|
|
|
|
public function multipleOptionsExample()
|
|
{
|
|
// Multiple options at once
|
|
flash()
|
|
->options([
|
|
'position' => 'top-center',
|
|
'timeout' => 3000,
|
|
'closeButton' => false
|
|
])
|
|
->success('Multiple options at once');
|
|
|
|
return back();
|
|
}
|
|
}</code></pre>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Additional features highlight -->
|
|
<div class="bg-slate-50 rounded-xl p-6 border border-slate-200 mt-12">
|
|
<h3 class="text-xl font-bold text-slate-800 mb-4">Additional Features</h3>
|
|
|
|
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<!-- Feature 1 -->
|
|
<div class="bg-white rounded-lg p-5 border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
|
<div class="w-10 h-10 rounded-full bg-amber-100 flex items-center justify-center mb-3">
|
|
<i class="fa-solid fa-puzzle-piece text-amber-600"></i>
|
|
</div>
|
|
<h4 class="font-semibold text-slate-800 mb-2">Multiple Themes</h4>
|
|
<p class="text-slate-600 text-sm">Choose from 6+ themes including Toastr, SweetAlert, Notyf, Noty and more.</p>
|
|
<a href="https://php-flasher.github.io/themes" class="text-emerald-600 text-xs font-medium mt-2 inline-flex items-center hover:text-emerald-800">
|
|
View themes <i class="fa-solid fa-arrow-right ml-1"></i>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Feature 2 -->
|
|
<div class="bg-white rounded-lg p-5 border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
|
<div class="w-10 h-10 rounded-full bg-emerald-100 flex items-center justify-center mb-3">
|
|
<i class="fa-solid fa-language text-emerald-600"></i>
|
|
</div>
|
|
<h4 class="font-semibold text-slate-800 mb-2">Internationalization</h4>
|
|
<p class="text-slate-600 text-sm">Support for multiple languages and right-to-left (RTL) layouts.</p>
|
|
<a href="https://php-flasher.github.io/i18n" class="text-emerald-600 text-xs font-medium mt-2 inline-flex items-center hover:text-emerald-800">
|
|
Learn more <i class="fa-solid fa-arrow-right ml-1"></i>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Feature 3 -->
|
|
<div class="bg-white rounded-lg p-5 border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
|
|
<div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mb-3">
|
|
<i class="fa-solid fa-bolt-lightning text-indigo-600"></i>
|
|
</div>
|
|
<h4 class="font-semibold text-slate-800 mb-2">JavaScript API</h4>
|
|
<p class="text-slate-600 text-sm">Access the full JavaScript API for creating notifications directly from the frontend.</p>
|
|
<a href="https://php-flasher.github.io/javascript-api" class="text-emerald-600 text-xs font-medium mt-2 inline-flex items-center hover:text-emerald-800">
|
|
Learn more <i class="fa-solid fa-arrow-right ml-1"></i>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer CTA -->
|
|
<div class="mt-8 mb-12">
|
|
<div
|
|
class="bg-gradient-to-tr from-emerald-600 to-teal-700 rounded-xl p-8 md:p-12 border border-emerald-500 shadow-md text-center md:text-left">
|
|
<div class="flex flex-col md:flex-row md:items-center">
|
|
<div class="md:w-3/4">
|
|
<h3 class="text-3xl font-bold text-white mb-3">Ready to enhance your Inertia.js app?</h3>
|
|
<p class="text-emerald-100 mb-6 md:mb-0 text-lg">Start using <strong><span
|
|
class="text-white">PHP<span class="text-emerald-200">Flasher</span></span></strong> today and
|
|
give your users beautiful notifications in minutes!</p>
|
|
</div>
|
|
<div class="md:w-1/4 md:text-right">
|
|
<a href="#installation"
|
|
class="inline-block bg-white text-emerald-700 font-medium px-6 py-3 rounded-lg hover:bg-emerald-50 transition-colors shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-transform">
|
|
Get Started Now
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.animate-float {
|
|
animation: float 6s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes float {
|
|
0%, 100% {
|
|
transform: translateY(0);
|
|
}
|
|
50% {
|
|
transform: translateY(-10px);
|
|
}
|
|
}
|
|
|
|
.animate-spin-slow {
|
|
animation: spin 20s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
from {
|
|
transform: rotate(0deg);
|
|
}
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.animate-pulse-slow {
|
|
animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
}
|
|
|
|
.animate-pulse-delayed {
|
|
animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) 1s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
.animate-fade-in {
|
|
animation: fadeIn 1s ease-out forwards;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.no-scrollbar {
|
|
-ms-overflow-style: none; /* IE and Edge */
|
|
scrollbar-width: none; /* Firefox */
|
|
}
|
|
|
|
.no-scrollbar::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// Tab switching functionality for frontend frameworks
|
|
function switchTab(tabId) {
|
|
// Hide all content sections
|
|
document.getElementById('vueContent').classList.add('hidden');
|
|
document.getElementById('reactContent').classList.add('hidden');
|
|
document.getElementById('svelteContent').classList.add('hidden');
|
|
|
|
// Show the selected content
|
|
document.getElementById(tabId + 'Content').classList.remove('hidden');
|
|
|
|
// Update tab styling
|
|
document.getElementById('vueTab').classList.remove('border-emerald-500', 'text-emerald-600');
|
|
document.getElementById('reactTab').classList.remove('border-emerald-500', 'text-emerald-600');
|
|
document.getElementById('svelteTab').classList.remove('border-emerald-500', 'text-emerald-600');
|
|
|
|
document.getElementById(tabId + 'Tab').classList.add('border-emerald-500', 'text-emerald-600');
|
|
}
|
|
|
|
// Initialize with Vue tab selected by default
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
switchTab('vue');
|
|
});
|
|
</script>
|
|
|
|
|
|
|
|
|