mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 03:31:10 -05:00
Implement HomeController, update home route, and enhance menu components with close functionality
This commit is contained in:
18
app/Http/Controllers/HomeController.php
Normal file
18
app/Http/Controllers/HomeController.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Park;
|
||||||
|
use App\Models\ParkArea;
|
||||||
|
|
||||||
|
class HomeController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('home', [
|
||||||
|
'total_parks' => Park::count(),
|
||||||
|
'total_attractions' => ParkArea::count(),
|
||||||
|
'total_coasters' => ParkArea::where('type', 'roller_coaster')->count(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,11 @@ class AuthMenuComponent extends Component
|
|||||||
$this->isOpen = !$this->isOpen;
|
$this->isOpen = !$this->isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
$this->isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.auth-menu-component');
|
return view('livewire.auth-menu-component');
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ class MobileMenuComponent extends Component
|
|||||||
$this->isOpen = !$this->isOpen;
|
$this->isOpen = !$this->isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
$this->isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.mobile-menu-component');
|
return view('livewire.mobile-menu-component');
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ class UserMenuComponent extends Component
|
|||||||
$this->isOpen = !$this->isOpen;
|
$this->isOpen = !$this->isOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
$this->isOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.user-menu-component');
|
return view('livewire.user-menu-component');
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('park_areas', function (Blueprint $table) {
|
||||||
|
$table->string('type')->default('attraction')->after('name');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('park_areas', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('type');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,129 +1,72 @@
|
|||||||
# Design Migration from Django to Laravel
|
# Design Migration
|
||||||
|
|
||||||
## Overview
|
## Header Design
|
||||||
This document tracks the migration of design assets and templates from the original Django project to Laravel/Livewire implementation.
|
- Solid background with subtle transparency (bg-gray-900)
|
||||||
|
- Increased height for better visibility (h-20)
|
||||||
|
- Consistent spacing with px-6
|
||||||
|
- Logo uses text gradient effect
|
||||||
|
- Navigation links properly spaced with hover effects
|
||||||
|
- Search bar integrated into header with proper styling
|
||||||
|
|
||||||
## Static Assets Structure (Django)
|
## Menu Components
|
||||||
```
|
|
||||||
static/
|
|
||||||
├── css/
|
|
||||||
│ ├── alerts.css
|
|
||||||
│ ├── tailwind.css
|
|
||||||
│ └── src/
|
|
||||||
│ └── input.css
|
|
||||||
├── images/
|
|
||||||
│ ├── default-avatar.png
|
|
||||||
│ ├── discord-icon.svg
|
|
||||||
│ ├── favicon.png
|
|
||||||
│ ├── google-icon.svg
|
|
||||||
│ └── placeholders/
|
|
||||||
│ ├── dark-ride.jpg
|
|
||||||
│ ├── default-park.jpg
|
|
||||||
│ ├── default-ride.jpg
|
|
||||||
│ ├── flat-ride.jpg
|
|
||||||
│ ├── other-ride.jpg
|
|
||||||
│ ├── roller-coaster.jpg
|
|
||||||
│ ├── transport.jpg
|
|
||||||
│ └── water-ride.jpg
|
|
||||||
└── js/
|
|
||||||
├── alerts.js
|
|
||||||
├── alpine.min.js
|
|
||||||
├── cdn.min.js
|
|
||||||
├── location-autocomplete.js
|
|
||||||
├── main.js
|
|
||||||
├── park-map.js
|
|
||||||
├── photo-gallery.js
|
|
||||||
└── search.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## Primary Templates (Django)
|
### Common Features
|
||||||
1. Base Templates
|
- Consistent backdrop blur effect
|
||||||
- base/base.html (Main layout template)
|
- Semi-transparent backgrounds (bg-gray-900/95)
|
||||||
|
- Subtle border effects (border-gray-800/50)
|
||||||
2. Feature-specific Templates
|
- Smooth transitions and animations
|
||||||
- accounts/ - User authentication and profile templates
|
- Click-away behavior for all dropdowns
|
||||||
- rides/ - Ride-related templates including listings and details
|
- Proper z-indexing and positioning
|
||||||
- parks/ - Park management templates
|
|
||||||
- companies/ - Company and manufacturer templates
|
|
||||||
- location/ - Location-related templates
|
|
||||||
- moderation/ - Content moderation templates
|
|
||||||
- media/ - Media management templates
|
|
||||||
|
|
||||||
## Migration Plan
|
### User Menu
|
||||||
|
- Larger profile picture (w-10 h-10)
|
||||||
|
- Username display in dropdown
|
||||||
|
- Sectioned menu items with borders
|
||||||
|
- Consistent hover states
|
||||||
|
- Clear visual hierarchy
|
||||||
|
|
||||||
### Phase 1: Core Assets
|
### Mobile Menu
|
||||||
1. Static Assets Migration
|
- Full-width design
|
||||||
- Copy and organize images in Laravel public directory
|
- Proper spacing (p-6)
|
||||||
- Set up Tailwind CSS with proper configuration
|
- Enhanced search bar visibility
|
||||||
- Migrate JavaScript assets to Laravel Vite setup
|
- Smooth slide-in animation
|
||||||
|
- Semi-transparent backdrop
|
||||||
|
- Proper touch targets
|
||||||
|
|
||||||
### Phase 2: Component Structure
|
### Auth Menu
|
||||||
1. Blade Components
|
- Wider dropdown (w-56)
|
||||||
- Convert Django templates to Blade components
|
- Clear login/register options
|
||||||
- Implement Livewire components for interactive features
|
- Consistent styling with other menus
|
||||||
- Maintain consistent naming and structure
|
- Proper icon alignment
|
||||||
|
|
||||||
### Phase 3: Layout & Design
|
### Theme Toggle
|
||||||
1. Base Layout
|
- Improved button states
|
||||||
- Implement base.blade.php mirroring Django base template
|
- Proper focus indicators
|
||||||
- Set up layout components and partials
|
- Smooth transition effects
|
||||||
- Configure asset compilation and delivery
|
- Clear light/dark mode icons
|
||||||
|
|
||||||
### Phase 4: Feature Templates
|
## Responsive Design
|
||||||
1. Systematic migration of feature-specific templates:
|
- Mobile-first approach
|
||||||
- Auth & Profile views
|
- Proper breakpoints for navigation
|
||||||
- Park management views
|
- Search bar visibility management
|
||||||
- Ride management views
|
- Menu adaptations for different screen sizes
|
||||||
- Company management views
|
|
||||||
- Location components
|
|
||||||
- Moderation interface
|
|
||||||
- Media management views
|
|
||||||
|
|
||||||
## Progress Tracking
|
## Color System
|
||||||
|
- Primary: Indigo-based gradient
|
||||||
|
- Secondary: Gray scale for UI elements
|
||||||
|
- Proper dark mode support
|
||||||
|
- Consistent hover states
|
||||||
|
- Semi-transparent overlays
|
||||||
|
|
||||||
- [x] Phase 1: Core Assets
|
## Typography
|
||||||
- [x] Image assets migration
|
- Poppins font family
|
||||||
- [x] CSS setup and migration
|
- Clear hierarchy in text sizes
|
||||||
- [x] JavaScript migration
|
- Proper line heights
|
||||||
|
- Consistent font weights
|
||||||
|
|
||||||
- [x] Phase 2: Component Structure
|
## Accessibility
|
||||||
- [x] Base components
|
- Proper focus states
|
||||||
- [x] Interactive components
|
- Clear hover indicators
|
||||||
- [x] Form components
|
- Sufficient color contrast
|
||||||
|
- Proper ARIA labels
|
||||||
- [x] Phase 3: Layout & Design
|
- Keyboard navigation support
|
||||||
- [x] Base layout
|
|
||||||
- [x] Navigation
|
|
||||||
- [x] Common elements
|
|
||||||
|
|
||||||
- [x] Phase 4: Feature Templates
|
|
||||||
- [x] Auth templates
|
|
||||||
- [x] Park templates
|
|
||||||
- [x] Ride templates
|
|
||||||
- [x] Company templates
|
|
||||||
- [x] Location templates
|
|
||||||
- [x] Moderation templates
|
|
||||||
- [x] Media templates
|
|
||||||
|
|
||||||
## Technical Decisions
|
|
||||||
|
|
||||||
### CSS Strategy
|
|
||||||
- Using Tailwind CSS for styling consistency
|
|
||||||
- Maintaining utility-first approach from Django project
|
|
||||||
- Reusing existing Tailwind configuration where possible
|
|
||||||
|
|
||||||
### JavaScript Strategy
|
|
||||||
- Leveraging Laravel's Vite for asset compilation
|
|
||||||
- Using Alpine.js for interactive features (matches Django implementation)
|
|
||||||
- Maintaining modular structure for JS components
|
|
||||||
|
|
||||||
### Component Strategy
|
|
||||||
- Converting Django template partials to Blade components
|
|
||||||
- Using Livewire for dynamic features
|
|
||||||
- Maintaining consistent naming conventions
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
1. Begin Phase 1 with static asset migration
|
|
||||||
2. Set up base layout structure
|
|
||||||
3. Implement core components
|
|
||||||
4. Migrate feature-specific templates systematically
|
|
||||||
125
memory-bank/design/MenuComponents.md
Normal file
125
memory-bank/design/MenuComponents.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
# Menu Components
|
||||||
|
|
||||||
|
## Menu Behavior (Auth, User, and Mobile)
|
||||||
|
|
||||||
|
All menu components implement consistent behavior for opening and closing using pure Livewire functionality:
|
||||||
|
|
||||||
|
1. **Click Outside to Close**
|
||||||
|
- Uses `wire:click.away="close"` to close menu when clicking outside
|
||||||
|
- Calls the Livewire `close()` method directly
|
||||||
|
|
||||||
|
2. **Toggle on Button Click**
|
||||||
|
- Uses `wire:click.stop="toggle"` on the trigger button/image
|
||||||
|
- Prevents event bubbling with wire:click.stop
|
||||||
|
- Toggle method directly flips the boolean state
|
||||||
|
- Simple and efficient state management
|
||||||
|
|
||||||
|
3. **Menu Styling and Behavior**
|
||||||
|
- Uses z-index to ensure proper stacking
|
||||||
|
- Full-width menu items for better clickability
|
||||||
|
- Consistent hover and focus states
|
||||||
|
- Left-aligned text in buttons for consistency
|
||||||
|
|
||||||
|
4. **Accessibility Features**
|
||||||
|
- Proper ARIA roles and attributes
|
||||||
|
- Focus management for keyboard navigation
|
||||||
|
- Clear visual feedback on focus
|
||||||
|
- Semantic HTML structure
|
||||||
|
|
||||||
|
## Mobile Menu Specific Features
|
||||||
|
|
||||||
|
1. **Backdrop Handling**
|
||||||
|
- Semi-transparent backdrop when menu is open
|
||||||
|
- Clicking backdrop closes menu
|
||||||
|
- Smooth opacity transitions
|
||||||
|
|
||||||
|
2. **Responsive Behavior**
|
||||||
|
- Hidden on larger screens (lg:hidden)
|
||||||
|
- Full-width menu on mobile
|
||||||
|
- Smooth slide and fade transitions
|
||||||
|
|
||||||
|
3. **Navigation Links**
|
||||||
|
- Full-width clickable areas
|
||||||
|
- Consistent spacing and padding
|
||||||
|
- Clear visual feedback on hover/focus
|
||||||
|
- Proper role attributes
|
||||||
|
|
||||||
|
### Implementation Details
|
||||||
|
|
||||||
|
All components share identical state management and methods:
|
||||||
|
|
||||||
|
```php
|
||||||
|
class MenuComponent extends Component
|
||||||
|
{
|
||||||
|
public bool $isOpen = false;
|
||||||
|
|
||||||
|
public function toggle()
|
||||||
|
{
|
||||||
|
$this->isOpen = !$this->isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
$this->isOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Menu buttons include proper accessibility attributes:
|
||||||
|
```blade
|
||||||
|
<button
|
||||||
|
wire:click.stop="toggle"
|
||||||
|
type="button"
|
||||||
|
role="button"
|
||||||
|
aria-expanded="{{ $isOpen }}"
|
||||||
|
aria-label="Menu name"
|
||||||
|
id="menu-button"
|
||||||
|
>
|
||||||
|
<!-- Button content -->
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Menu containers have proper ARIA roles:
|
||||||
|
```blade
|
||||||
|
<div
|
||||||
|
wire:key="menu-name"
|
||||||
|
wire:click.away="close"
|
||||||
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby="menu-button"
|
||||||
|
class="... {{ $isOpen ? 'visible-state' : 'hidden-state' }}"
|
||||||
|
>
|
||||||
|
```
|
||||||
|
|
||||||
|
Menu items have consistent styling and accessibility:
|
||||||
|
```blade
|
||||||
|
<a
|
||||||
|
href="{{ route('item.route') }}"
|
||||||
|
role="menuitem"
|
||||||
|
class="flex items-center w-full gap-3 px-4 py-3 text-gray-300 transition-colors hover:text-white hover:bg-gray-800/50 focus:outline-none focus:text-white focus:bg-gray-800/50"
|
||||||
|
>
|
||||||
|
<i class="w-5 fas fa-icon"></i>
|
||||||
|
<span>Menu Item</span>
|
||||||
|
</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
Mobile menu backdrop:
|
||||||
|
```blade
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 bg-black/50 transition-opacity duration-300 lg:hidden {{ $isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none' }}"
|
||||||
|
wire:click="close"
|
||||||
|
></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
This implementation:
|
||||||
|
- Uses pure Livewire without Alpine.js dependencies
|
||||||
|
- Maintains smooth transitions through CSS classes
|
||||||
|
- Ensures consistent behavior across all menu components
|
||||||
|
- Properly handles both click-outside and toggle functionality
|
||||||
|
- Prevents event bubbling with wire:click.stop
|
||||||
|
- Ensures proper re-rendering with wire:key
|
||||||
|
- Uses simplified toggle logic for better reliability
|
||||||
|
- Provides consistent styling and behavior for all menu items
|
||||||
|
- Implements proper accessibility features throughout
|
||||||
|
- Supports both mouse and keyboard interactions
|
||||||
|
- Handles mobile-specific requirements elegantly
|
||||||
38
resources/views/home.blade.php
Normal file
38
resources/views/home.blade.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<x-app-layout>
|
||||||
|
@section('title', 'ThrillWiki - Your Ultimate Theme Park Guide')
|
||||||
|
<div class="flex flex-col items-center justify-center min-h-[70vh] text-center">
|
||||||
|
<div class="w-full max-w-4xl px-4">
|
||||||
|
<h1 class="mb-8 text-6xl font-bold text-white">
|
||||||
|
Welcome to ThrillWiki
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p class="mb-16 text-2xl text-gray-300">
|
||||||
|
Your ultimate guide to theme parks and attractions worldwide
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-center gap-4">
|
||||||
|
<a href="{{ route('parks.index') }}" class="px-10 py-4 text-xl font-semibold text-white transition-transform bg-indigo-600 rounded-lg hover:scale-105">
|
||||||
|
Explore Parks
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('rides.index') }}" class="px-10 py-4 text-xl font-semibold text-white transition-transform border rounded-lg hover:scale-105 border-white/20">
|
||||||
|
View Rides
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 gap-8 mt-20 sm:grid-cols-3">
|
||||||
|
<div class="p-8 text-center rounded-lg bg-gray-900/50">
|
||||||
|
<div class="text-6xl font-bold text-white">{{ $total_parks ?? 0 }}</div>
|
||||||
|
<div class="mt-3 text-xl text-gray-300">Theme Parks</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-8 text-center rounded-lg bg-gray-900/50">
|
||||||
|
<div class="text-6xl font-bold text-white">{{ $total_attractions ?? 0 }}</div>
|
||||||
|
<div class="mt-3 text-xl text-gray-300">Attractions</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-8 text-center rounded-lg bg-gray-900/50">
|
||||||
|
<div class="text-6xl font-bold text-white">{{ $total_coasters ?? 0 }}</div>
|
||||||
|
<div class="mt-3 text-xl text-gray-300">Roller Coasters</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-app-layout>
|
||||||
@@ -64,66 +64,73 @@
|
|||||||
@stack('styles')
|
@stack('styles')
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
class="flex flex-col min-h-screen text-gray-900 bg-gradient-to-br from-white via-blue-50 to-indigo-50 dark:from-gray-950 dark:via-indigo-950 dark:to-purple-950 dark:text-white"
|
class="flex flex-col min-h-screen text-gray-900 bg-gradient-to-br from-white via-blue-50 to-indigo-50 dark:from-gray-950 dark:via-blue-950 dark:to-purple-950 dark:text-white"
|
||||||
>
|
>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header
|
<header class="sticky top-0 z-40 bg-gray-900 shadow-lg">
|
||||||
class="sticky top-0 z-40 border-b shadow-lg bg-white/90 dark:bg-gray-800/90 backdrop-blur-lg border-gray-200/50 dark:border-gray-700/50"
|
<nav class="container mx-auto">
|
||||||
>
|
<div class="flex items-center justify-between h-20 px-6">
|
||||||
<nav class="container mx-auto nav-container">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<a
|
<a href="{{ route('home') }}" class="text-2xl font-bold text-white transition-transform hover:scale-105">
|
||||||
href="{{ route('home') }}"
|
<span class="text-gradient">ThrillWiki</span>
|
||||||
class="font-bold text-transparent transition-transform site-logo bg-gradient-to-r from-primary to-secondary bg-clip-text hover:scale-105"
|
|
||||||
>
|
|
||||||
ThrillWiki
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navigation Links (Always Visible) -->
|
<!-- Navigation Links (Always Visible) -->
|
||||||
<div class="flex items-center space-x-2 sm:space-x-4">
|
<div class="hidden lg:flex items-center space-x-8">
|
||||||
<a href="{{ route('parks.index') }}" class="nav-link">
|
<a href="{{ route('parks.index') }}" class="nav-link">
|
||||||
<i class="fas fa-map-marker-alt"></i>
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2L2 9L12 16L22 9L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 9V20L12 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M22 9V20L12 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
<span>Parks</span>
|
<span>Parks</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('rides.index') }}" class="nav-link">
|
<a href="{{ route('rides.index') }}" class="nav-link">
|
||||||
<i class="fas fa-rocket"></i>
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M22 12H18L15 21L9 3L6 12H2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
<span>Rides</span>
|
<span>Rides</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search Bar -->
|
<!-- Search Bar -->
|
||||||
<div class="flex-1 hidden max-w-md mx-8 lg:flex">
|
<div class="flex-1 max-w-xl mx-8 hidden lg:block">
|
||||||
<form action="{{ route('search') }}" method="get" class="w-full">
|
<form action="{{ route('search') }}" method="get" class="w-full">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="q"
|
name="q"
|
||||||
placeholder="Search parks and rides..."
|
placeholder="Search parks and rides..."
|
||||||
class="form-input"
|
class="w-full px-4 py-2 text-gray-200 bg-gray-800/50 border border-gray-700/50 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary placeholder-gray-400"
|
||||||
/>
|
/>
|
||||||
|
<button type="submit" class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||||
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right Side Menu -->
|
<!-- Right Side Menu -->
|
||||||
<div class="flex items-center space-x-2 sm:space-x-6">
|
<div class="flex items-center space-x-6">
|
||||||
<!-- Theme Toggle -->
|
<!-- Theme Toggle -->
|
||||||
<livewire:theme-toggle-component />
|
<livewire:theme-toggle-component />
|
||||||
|
|
||||||
<!-- User Menu -->
|
<!-- User Menu -->
|
||||||
@auth
|
@auth
|
||||||
@if(auth()->user()->can('access-moderation'))
|
@if(auth()->user()->can('access-moderation'))
|
||||||
<a href="{{ route('moderation.dashboard') }}" class="nav-link">
|
<a href="{{ route('moderation.dashboard') }}" class="flex items-center gap-2 text-gray-300 hover:text-white">
|
||||||
<i class="fas fa-shield-alt"></i>
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
<span>Moderation</span>
|
<span>Moderation</span>
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
<livewire:user-menu-component />
|
<livewire:user-menu-component />
|
||||||
@else
|
@else
|
||||||
<!-- Generic Profile Icon for Unauthenticated Users -->
|
|
||||||
<livewire:auth-menu-component />
|
<livewire:auth-menu-component />
|
||||||
@endauth
|
@endauth
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,45 @@
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div
|
<button
|
||||||
wire:click="toggle"
|
wire:click.stop="toggle"
|
||||||
class="flex items-center justify-center w-8 h-8 text-gray-500 transition-transform rounded-full cursor-pointer hover:text-primary dark:text-gray-400 dark:hover:text-primary hover:scale-105"
|
type="button"
|
||||||
|
role="button"
|
||||||
|
aria-expanded="{{ $isOpen }}"
|
||||||
|
aria-label="Authentication menu"
|
||||||
|
class="p-2 text-gray-300 transition-all duration-200 hover:text-white hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary/20 rounded-lg"
|
||||||
>
|
>
|
||||||
<i class="text-xl fas fa-user"></i>
|
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
</div>
|
<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2M12 11a4 4 0 100-8 4 4 0 000 8z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- Auth Menu -->
|
<!-- Auth Menu -->
|
||||||
<div
|
<div
|
||||||
wire:model="isOpen"
|
wire:key="auth-menu"
|
||||||
class="bg-white dropdown-menu dark:bg-gray-800"
|
wire:click.away="close"
|
||||||
style="display: {{ $isOpen ? 'block' : 'none' }}"
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby="auth-menu-button"
|
||||||
|
class="absolute right-0 z-50 w-56 mt-2 overflow-hidden transition-all duration-200 transform origin-top-right bg-gray-900/95 backdrop-blur-lg rounded-lg shadow-xl border border-gray-800/50 {{ $isOpen ? 'scale-100 opacity-100' : 'scale-95 opacity-0 pointer-events-none' }}"
|
||||||
>
|
>
|
||||||
<div
|
<a
|
||||||
hx-get="{{ route('login') }}"
|
href="{{ route('login') }}"
|
||||||
hx-target="body"
|
role="menuitem"
|
||||||
hx-swap="beforeend"
|
class="flex items-center w-full gap-3 px-4 py-3 text-gray-300 transition-colors hover:text-white hover:bg-gray-800/50 focus:outline-none focus:text-white focus:bg-gray-800/50"
|
||||||
class="cursor-pointer menu-item"
|
|
||||||
>
|
>
|
||||||
<i class="w-5 fas fa-sign-in-alt"></i>
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15 3h4a2 2 0 012 2v14a2 2 0 01-2 2h-4M10 17l5-5-5-5M15 12H3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
<span>Login</span>
|
<span>Login</span>
|
||||||
</div>
|
</a>
|
||||||
<div
|
<a
|
||||||
hx-get="{{ route('register') }}"
|
href="{{ route('register') }}"
|
||||||
hx-target="body"
|
role="menuitem"
|
||||||
hx-swap="beforeend"
|
class="flex items-center w-full gap-3 px-4 py-3 text-gray-300 transition-colors hover:text-white hover:bg-gray-800/50 focus:outline-none focus:text-white focus:bg-gray-800/50"
|
||||||
class="cursor-pointer menu-item"
|
|
||||||
>
|
>
|
||||||
<i class="w-5 fas fa-user-plus"></i>
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M16 21v-2a4 4 0 00-4-4H6a4 4 0 00-4 4v2M12 11a4 4 0 100-8 4 4 0 000 8zM20 8v6M23 11h-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
<span>Register</span>
|
<span>Register</span>
|
||||||
</div>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,39 +1,96 @@
|
|||||||
<div>
|
<div class="relative">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div
|
||||||
|
class="fixed inset-0 bg-black/50 transition-opacity duration-300 lg:hidden {{ $isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none' }}"
|
||||||
|
wire:click="close"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<!-- Menu Button -->
|
||||||
<button
|
<button
|
||||||
wire:click="toggle"
|
wire:click.stop="toggle"
|
||||||
class="p-2 text-gray-500 rounded-lg lg:hidden hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-gray-400"
|
type="button"
|
||||||
|
role="button"
|
||||||
|
id="mobile-menu-button"
|
||||||
|
class="p-2 text-gray-300 transition-transform hover:text-white hover:scale-110 lg:hidden"
|
||||||
aria-label="Toggle mobile menu"
|
aria-label="Toggle mobile menu"
|
||||||
aria-expanded="{{ $isOpen }}"
|
aria-expanded="{{ $isOpen }}"
|
||||||
|
aria-controls="mobile-menu"
|
||||||
>
|
>
|
||||||
<i class="text-2xl fas {{ $isOpen ? 'fa-times' : 'fa-bars' }}"></i>
|
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
@if($isOpen)
|
||||||
|
<path d="M6 18L18 6M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
@else
|
||||||
|
<path d="M4 6h16M4 12h16M4 18h16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
@endif
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Mobile Menu -->
|
<!-- Mobile Menu -->
|
||||||
<div
|
<div
|
||||||
wire:model="isOpen"
|
wire:key="mobile-menu"
|
||||||
class="absolute left-0 right-0 w-full p-4 mt-2 space-y-4 bg-white border-b dark:bg-gray-800 dark:border-gray-700"
|
wire:click.away="close"
|
||||||
style="display: {{ $isOpen ? 'block' : 'none' }}"
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby="mobile-menu-button"
|
||||||
|
class="absolute left-0 right-0 z-50 w-full mt-2 bg-gray-900/95 backdrop-blur-lg border-t border-gray-800/50 shadow-xl transform transition-all duration-300 ease-out origin-top {{ $isOpen ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-2 pointer-events-none' }}"
|
||||||
>
|
>
|
||||||
<!-- Search (Mobile) -->
|
<!-- Search (Mobile) -->
|
||||||
<form action="{{ route('search') }}" method="get" class="mb-4">
|
<div class="p-6 border-b border-gray-800/50">
|
||||||
<input
|
<form action="{{ route('search') }}" method="get">
|
||||||
type="text"
|
<div class="relative">
|
||||||
name="q"
|
<input
|
||||||
placeholder="Search parks and rides..."
|
type="text"
|
||||||
class="form-input"
|
name="q"
|
||||||
/>
|
placeholder="Search parks and rides..."
|
||||||
</form>
|
class="w-full px-4 py-3 text-gray-200 bg-gray-800/50 border border-gray-700/50 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary placeholder-gray-400"
|
||||||
|
/>
|
||||||
|
<button type="submit" class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400">
|
||||||
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M21 21L15 15M17 10C17 13.866 13.866 17 10 17C6.13401 17 3 13.866 3 10C3 6.13401 6.13401 3 10 3C13.866 3 17 6.13401 17 10Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Mobile Navigation Links -->
|
<!-- Mobile Navigation Links -->
|
||||||
<nav class="space-y-2">
|
<nav class="p-6 space-y-2">
|
||||||
<a href="{{ route('parks.index') }}" class="block nav-link">
|
<a
|
||||||
<i class="fas fa-map-marker-alt"></i>
|
href="{{ route('parks.index') }}"
|
||||||
|
role="menuitem"
|
||||||
|
class="flex items-center w-full gap-3 px-4 py-3 text-gray-300 transition-colors rounded-lg hover:text-white hover:bg-gray-800/50 focus:outline-none focus:text-white focus:bg-gray-800/50"
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 2L2 9L12 16L22 9L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M2 9V20L12 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M22 9V20L12 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
<span>Parks</span>
|
<span>Parks</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('rides.index') }}" class="block nav-link">
|
<a
|
||||||
<i class="fas fa-rocket"></i>
|
href="{{ route('rides.index') }}"
|
||||||
|
role="menuitem"
|
||||||
|
class="flex items-center w-full gap-3 px-4 py-3 text-gray-300 transition-colors rounded-lg hover:text-white hover:bg-gray-800/50 focus:outline-none focus:text-white focus:bg-gray-800/50"
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M22 12H18L15 21L9 3L6 12H2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
<span>Rides</span>
|
<span>Rides</span>
|
||||||
</a>
|
</a>
|
||||||
|
@auth
|
||||||
|
@if(auth()->user()->can('access-moderation'))
|
||||||
|
<a
|
||||||
|
href="{{ route('moderation.dashboard') }}"
|
||||||
|
role="menuitem"
|
||||||
|
class="flex items-center w-full gap-3 px-4 py-3 text-gray-300 transition-colors rounded-lg hover:text-white hover:bg-gray-800/50 focus:outline-none focus:text-white focus:bg-gray-800/50"
|
||||||
|
>
|
||||||
|
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
<span>Moderation</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
@endauth
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
<label for="theme-toggle" class="cursor-pointer">
|
<button
|
||||||
<input
|
wire:click="toggleTheme"
|
||||||
type="checkbox"
|
class="p-2 text-gray-300 transition-all duration-200 hover:text-white hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary/20 rounded-lg"
|
||||||
id="theme-toggle"
|
aria-label="Toggle dark mode"
|
||||||
class="hidden"
|
title="{{ $isDark ? 'Switch to light mode' : 'Switch to dark mode' }}"
|
||||||
wire:model.live="isDark"
|
>
|
||||||
wire:change="toggleTheme"
|
<svg class="w-6 h-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
>
|
@if($isDark)
|
||||||
<div
|
<!-- Sun icon -->
|
||||||
class="inline-flex items-center justify-center p-2 text-gray-500 transition-colors hover:text-primary dark:text-gray-400 dark:hover:text-primary theme-toggle-btn"
|
<path d="M12 3v1m0 16v1m-8-9H3m18 0h-1m-2.293-6.293l-.707-.707M6.707 17.707l-.707.707M17.707 17.707l.707.707M6.707 6.707l-.707-.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
role="button"
|
@else
|
||||||
aria-label="Toggle dark mode"
|
<!-- Moon icon -->
|
||||||
>
|
<path d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<i class="text-xl fas {{ $isDark ? 'fa-sun' : 'fa-moon' }}"></i>
|
@endif
|
||||||
</div>
|
</svg>
|
||||||
</label>
|
</button>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('livewire:init', () => {
|
document.addEventListener('livewire:init', () => {
|
||||||
|
|||||||
@@ -1,47 +1,85 @@
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
<!-- Profile Picture Button -->
|
<!-- Profile Picture Button -->
|
||||||
@if(auth()->user()->profile?->avatar)
|
@if(auth()->user()->profile?->avatar)
|
||||||
<img
|
<button
|
||||||
wire:click="toggle"
|
wire:click.stop="toggle"
|
||||||
src="{{ auth()->user()->profile->avatar }}"
|
type="button"
|
||||||
alt="{{ auth()->user()->username }}"
|
role="button"
|
||||||
class="w-8 h-8 transition-transform rounded-full cursor-pointer ring-2 ring-primary/20 hover:scale-105"
|
aria-expanded="{{ $isOpen }}"
|
||||||
/>
|
aria-label="User menu"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="{{ auth()->user()->profile->avatar }}"
|
||||||
|
alt="{{ auth()->user()->username }}"
|
||||||
|
class="w-10 h-10 transition-all duration-200 rounded-full cursor-pointer ring-2 ring-primary/20 hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary/40"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
@else
|
@else
|
||||||
<div
|
<button
|
||||||
wire:click="toggle"
|
wire:click.stop="toggle"
|
||||||
class="flex items-center justify-center w-8 h-8 text-white transition-transform rounded-full cursor-pointer bg-gradient-to-br from-primary to-secondary hover:scale-105"
|
type="button"
|
||||||
|
role="button"
|
||||||
|
aria-expanded="{{ $isOpen }}"
|
||||||
|
aria-label="User menu"
|
||||||
|
class="flex items-center justify-center w-10 h-10 text-lg font-semibold text-white transition-all duration-200 rounded-full cursor-pointer bg-gradient-to-br from-primary to-secondary hover:scale-105 focus:outline-none focus:ring-2 focus:ring-primary/40"
|
||||||
>
|
>
|
||||||
{{ ucfirst(auth()->user()->username[0]) }}
|
{{ ucfirst(auth()->user()->username[0]) }}
|
||||||
</div>
|
</button>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<!-- Dropdown Menu -->
|
<!-- Dropdown Menu -->
|
||||||
<div
|
<div
|
||||||
wire:model="isOpen"
|
wire:key="user-menu"
|
||||||
class="bg-white dropdown-menu dark:bg-gray-800"
|
wire:click.away="close"
|
||||||
style="display: {{ $isOpen ? 'block' : 'none' }}"
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby="user-menu-button"
|
||||||
|
class="absolute right-0 z-50 w-56 mt-2 overflow-hidden transition-all duration-200 transform origin-top-right bg-gray-900/95 backdrop-blur-lg rounded-lg shadow-xl border border-gray-800/50 {{ $isOpen ? 'scale-100 opacity-100' : 'scale-95 opacity-0 pointer-events-none' }}"
|
||||||
>
|
>
|
||||||
<a href="{{ route('profile.show', auth()->user()->username) }}" class="menu-item">
|
<div class="px-4 py-3 border-b border-gray-800/50">
|
||||||
<i class="w-5 fas fa-user"></i>
|
<p class="text-sm text-gray-400">Signed in as</p>
|
||||||
<span>Profile</span>
|
<p class="text-sm font-medium text-white truncate">{{ auth()->user()->username }}</p>
|
||||||
</a>
|
</div>
|
||||||
<a href="{{ route('settings') }}" class="menu-item">
|
<div class="py-1">
|
||||||
<i class="w-5 fas fa-cog"></i>
|
<a
|
||||||
<span>Settings</span>
|
href="{{ route('profile.show', auth()->user()->username) }}"
|
||||||
</a>
|
class="flex items-center w-full gap-3 px-4 py-2 text-gray-300 transition-colors hover:text-white hover:bg-gray-800/50 focus:outline-none focus:text-white focus:bg-gray-800/50"
|
||||||
@if(auth()->user()->can('access-admin'))
|
role="menuitem"
|
||||||
<a href="{{ route('admin.index') }}" class="menu-item">
|
>
|
||||||
<i class="w-5 fas fa-shield-alt"></i>
|
<i class="w-5 fas fa-user"></i>
|
||||||
<span>Admin</span>
|
<span>Profile</span>
|
||||||
</a>
|
</a>
|
||||||
@endif
|
<a
|
||||||
<form method="POST" action="{{ route('logout') }}">
|
href="{{ route('settings') }}"
|
||||||
@csrf
|
class="flex items-center w-full gap-3 px-4 py-2 text-gray-300 transition-colors hover:text-white hover:bg-gray-800/50 focus:outline-none focus:text-white focus:bg-gray-800/50"
|
||||||
<button type="submit" class="w-full menu-item">
|
role="menuitem"
|
||||||
<i class="w-5 fas fa-sign-out-alt"></i>
|
>
|
||||||
<span>Logout</span>
|
<i class="w-5 fas fa-cog"></i>
|
||||||
</button>
|
<span>Settings</span>
|
||||||
</form>
|
</a>
|
||||||
|
@if(auth()->user()->can('access-admin'))
|
||||||
|
<a
|
||||||
|
href="{{ route('admin.index') }}"
|
||||||
|
class="flex items-center w-full gap-3 px-4 py-2 text-gray-300 transition-colors hover:text-white hover:bg-gray-800/50 focus:outline-none focus:text-white focus:bg-gray-800/50"
|
||||||
|
role="menuitem"
|
||||||
|
>
|
||||||
|
<i class="w-5 fas fa-shield-alt"></i>
|
||||||
|
<span>Admin</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="py-1 border-t border-gray-800/50">
|
||||||
|
<form method="POST" action="{{ route('logout') }}">
|
||||||
|
@csrf
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="flex items-center w-full gap-3 px-4 py-2 text-left text-gray-300 transition-colors hover:text-white hover:bg-gray-800/50 focus:outline-none focus:text-white focus:bg-gray-800/50"
|
||||||
|
role="menuitem"
|
||||||
|
>
|
||||||
|
<i class="w-5 fas fa-sign-out-alt"></i>
|
||||||
|
<span>Logout</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3,9 +3,7 @@
|
|||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use App\Livewire\Counter;
|
use App\Livewire\Counter;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', [App\Http\Controllers\HomeController::class, 'index'])->name('home');
|
||||||
return view('welcome');
|
|
||||||
})->name('home');
|
|
||||||
|
|
||||||
Route::get('/counter', Counter::class);
|
Route::get('/counter', Counter::class);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user