feature: ConnectionProvider integration
- Added `ConnectionProviderData`, `NavItemData`, and `NavSubItemData` DTOs for structured data handling. - Implemented CRUD operations for `ConnectionProviderService`, including transaction-safe create, update, and delete methods. - Introduced Livewire components for ConnectionProvider list, edit, and create pages with dynamic and reusable UI elements. - Refactored routes to consolidate paths under the `apps` prefix and updated navigation to dynamically include new sections for providers. - Enhanced sidebar with structured submenu for ConnectionProviders and Settings using dynamic collections. - Updated existing Create and Edit templates to use consistent route naming and validation handling. - Replaced hardcoded navigation items with dynamically generated structures leveraging the `NavItemData` collection.
This commit is contained in:
parent
bbb00c12b0
commit
48d2c1a47e
2
CONTRIBUTING.MD
Normal file
2
CONTRIBUTING.MD
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Always use `#[NoDiscard]` attribute in function which fetches data from DB. This ensures that
|
||||||
|
db does not called unnecessarily.
|
||||||
20
app/Data/ConnectedApp/ConnectionProviderData.php
Normal file
20
app/Data/ConnectedApp/ConnectionProviderData.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Data\ConnectedApp;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Attributes\MapInputName;
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
|
||||||
|
|
||||||
|
#[MapInputName(SnakeCaseMapper::class)]
|
||||||
|
class ConnectionProviderData extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $id,
|
||||||
|
public string $name,
|
||||||
|
public string $slug
|
||||||
|
) {}
|
||||||
|
|
||||||
|
}
|
||||||
20
app/Data/Ui/Sidebar/NavItemData.php
Normal file
20
app/Data/Ui/Sidebar/NavItemData.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Data\Ui\Sidebar;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||||
|
use Spatie\LaravelData\{Data, DataCollection};
|
||||||
|
|
||||||
|
final class NavItemData extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $label,
|
||||||
|
public string $icon,
|
||||||
|
public string $active_pattern,
|
||||||
|
public ?string $route = null,
|
||||||
|
#[DataCollectionOf(NavSubItemData::class)]
|
||||||
|
public ?DataCollection $sub_menu = null,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
16
app/Data/Ui/Sidebar/NavSubItemData.php
Normal file
16
app/Data/Ui/Sidebar/NavSubItemData.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Data\Ui\Sidebar;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
final class NavSubItemData extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $label,
|
||||||
|
public string $route,
|
||||||
|
public string $active_pattern,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@ -4,9 +4,8 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Forms;
|
namespace App\Livewire\Forms;
|
||||||
|
|
||||||
use App\Data\ConnectedApp\StoreConnectionProviderRequestData;
|
use App\Data\ConnectedApp\ConnectionProviderData;
|
||||||
use App\Models\ConnectionProvider;
|
use App\Models\ConnectionProvider;
|
||||||
use App\Services\ConnectionProviderService;
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Livewire\Form;
|
use Livewire\Form;
|
||||||
@ -15,6 +14,11 @@ class StoreConnectionProviderForm extends Form
|
|||||||
{
|
{
|
||||||
public string $name = '';
|
public string $name = '';
|
||||||
|
|
||||||
|
public function set(ConnectionProviderData $data): void
|
||||||
|
{
|
||||||
|
$this->name = $data->name;
|
||||||
|
}
|
||||||
|
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -39,14 +43,4 @@ function (string $attribute, mixed $value, Closure $fail): void {
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(): void
|
|
||||||
{
|
|
||||||
$this->validate();
|
|
||||||
$service = app(ConnectionProviderService::class);
|
|
||||||
$data = new StoreConnectionProviderRequestData(
|
|
||||||
name: $this->name,
|
|
||||||
);
|
|
||||||
$service->create($data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,14 +4,22 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Data\ConnectedApp\{ConnectionProviderData, StoreConnectionProviderRequestData};
|
||||||
use App\Models\ConnectionProvider;
|
use App\Models\ConnectionProvider;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use NoDiscard;
|
||||||
|
use Spatie\LaravelData\PaginatedDataCollection;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class ConnectionProviderService
|
class ConnectionProviderService
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return Collection<int, ConnectionProvider>
|
* @return Collection<int, ConnectionProvider>
|
||||||
*/
|
*/
|
||||||
|
#[NoDiscard]
|
||||||
public function search(string $value): Collection
|
public function search(string $value): Collection
|
||||||
{
|
{
|
||||||
return ConnectionProvider::query()
|
return ConnectionProvider::query()
|
||||||
@ -19,4 +27,68 @@ public function search(string $value): Collection
|
|||||||
->orWhereLike('name', "%{$value}%")
|
->orWhereLike('name', "%{$value}%")
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Throwable
|
||||||
|
*/
|
||||||
|
public function create(StoreConnectionProviderRequestData $data): void
|
||||||
|
{
|
||||||
|
DB::transaction(
|
||||||
|
function () use ($data): void {
|
||||||
|
ConnectionProvider::query()->create($data->toArray());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return PaginatedDataCollection<int, ConnectionProviderData>
|
||||||
|
*/
|
||||||
|
#[NoDiscard]
|
||||||
|
public function getAll(): PaginatedDataCollection
|
||||||
|
{
|
||||||
|
$data = ConnectionProvider::query()->select(['id', 'name', 'slug'])->paginate();
|
||||||
|
|
||||||
|
return ConnectionProviderData::collect($data, PaginatedDataCollection::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidArgumentException when id is invalid
|
||||||
|
* @throws Throwable when an error occurs during the update.
|
||||||
|
*/
|
||||||
|
public function update(string $slug, StoreConnectionProviderRequestData $data): void
|
||||||
|
{
|
||||||
|
DB::transaction(
|
||||||
|
fn () => ConnectionProvider::query()
|
||||||
|
->where('slug', $slug)
|
||||||
|
->update($data->toArray())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Throwable when an error occurs during the deletion.
|
||||||
|
* @throws InvalidArgumentException when id is invalid
|
||||||
|
*/
|
||||||
|
public function delete(string $slug): void
|
||||||
|
{
|
||||||
|
DB::transaction(
|
||||||
|
fn () => ConnectionProvider::query()
|
||||||
|
->where('slug', $slug)
|
||||||
|
->delete()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ModelNotFoundException<ConnectionProviderData> when the connected app is not found.
|
||||||
|
* @throws InvalidArgumentException when id is invalid.
|
||||||
|
*/
|
||||||
|
#[NoDiscard]
|
||||||
|
public function getProvider(string $slug): ConnectionProviderData
|
||||||
|
{
|
||||||
|
$data = ConnectionProvider::query()
|
||||||
|
->select(['id', 'name', 'slug'])
|
||||||
|
->where('slug', $slug)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return ConnectionProviderData::from($data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Data\Ui\Sidebar\NavItemData;
|
||||||
|
use App\Data\Ui\Sidebar\NavSubItemData;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Spatie\LaravelData\DataCollection;
|
||||||
|
|
||||||
new class extends Component {
|
new class extends Component {
|
||||||
/** Collapsed = icons-only mode */
|
/** Collapsed = icons-only mode */
|
||||||
@ -17,29 +20,55 @@ public function toggle(): void
|
|||||||
session(['sidebar_collapsed' => $this->collapsed]);
|
session(['sidebar_collapsed' => $this->collapsed]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function navItems(): array
|
/**
|
||||||
|
* @return DataCollection<int, NavItemData>
|
||||||
|
*/
|
||||||
|
public function navItems(): DataCollection
|
||||||
{
|
{
|
||||||
return [
|
return NavItemData::collect([
|
||||||
[
|
new NavItemData(
|
||||||
'label' => 'Dashboard',
|
label: 'Dashboard',
|
||||||
'icon' => 'lucide.house',
|
icon: 'lucide.house',
|
||||||
'route' => 'dashboard',
|
active_pattern: 'dashboard',
|
||||||
'active_pattern' => 'dashboard',
|
route: 'dashboard',
|
||||||
],
|
),
|
||||||
[
|
|
||||||
'label' => 'Users',
|
new NavItemData(
|
||||||
'icon' => 'lucide.users',
|
label: 'Apps',
|
||||||
'route' => 'home',
|
icon: 'lucide.grid-2x2-plus',
|
||||||
'active_pattern' => 'home',
|
active_pattern: 'apps.*',
|
||||||
],
|
sub_menu: NavSubItemData::collect([
|
||||||
[
|
new NavSubItemData(
|
||||||
'label' => 'Apps',
|
label: 'All Apps',
|
||||||
'icon' => 'lucide.grid-2x2-plus',
|
route: 'apps.index',
|
||||||
'route' => 'connected-apps.index',
|
active_pattern: 'apps.index'
|
||||||
// Using a wildcard ensures the tab stays active on nested routes like connected-apps.edit
|
),
|
||||||
'active_pattern' => 'connected-apps.*',
|
new NavSubItemData(
|
||||||
],
|
label: 'Providers',
|
||||||
];
|
route: 'apps.connectionProviders.index',
|
||||||
|
active_pattern: 'apps.connectionProviders.*'
|
||||||
|
)
|
||||||
|
], DataCollection::class)
|
||||||
|
),
|
||||||
|
|
||||||
|
new NavItemData(
|
||||||
|
label: 'Settings',
|
||||||
|
icon: 'lucide.settings',
|
||||||
|
active_pattern: 'settings.*',
|
||||||
|
sub_menu: NavSubItemData::collect([
|
||||||
|
new NavSubItemData(
|
||||||
|
label: 'Profile',
|
||||||
|
route: 'profile.edit',
|
||||||
|
active_pattern: 'settings.profile',
|
||||||
|
),
|
||||||
|
new NavSubItemData(
|
||||||
|
label: 'Security',
|
||||||
|
route: 'security.edit',
|
||||||
|
active_pattern: 'settings.security',
|
||||||
|
),
|
||||||
|
], DataCollection::class)
|
||||||
|
),
|
||||||
|
], DataCollection::class);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
?>
|
?>
|
||||||
@ -81,72 +110,115 @@ class="ml-3 text-sm font-semibold text-gray-900 whitespace-nowrap tracking-tight
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav
|
<nav
|
||||||
class="flex-1 py-3 px-2 space-y-0.5 overflow-y-auto overflow-x-hidden"
|
class="flex-1 py-3 px-2 space-y-1 overflow-y-auto overflow-x-hidden"
|
||||||
aria-label="Main navigation"
|
aria-label="Main navigation"
|
||||||
>
|
>
|
||||||
@foreach ($this->navItems() as $item)
|
@foreach ($this->navItems() as $item)
|
||||||
{{-- Check if the current route matches the pattern --}}
|
@php $isActive = request()->routeIs($item->active_pattern); @endphp
|
||||||
@php $isActive = request()->routeIs($item['active_pattern']); @endphp
|
|
||||||
|
|
||||||
<a
|
@if ($item->sub_menu)
|
||||||
href="{{ route($item['route']) }}"
|
{{-- ITEM WITH SUBMENU --}}
|
||||||
wire:navigate
|
<div x-data="{ expanded: {{ $isActive ? 'true' : 'false' }} }">
|
||||||
@class([
|
<button
|
||||||
'group relative flex items-center gap-3 px-2 py-2.5 rounded-lg',
|
@click="expanded = !expanded; if(collapsed) collapsed = false"
|
||||||
'text-sm font-medium transition-colors duration-150',
|
@class([
|
||||||
'bg-gray-100 text-gray-900' => $isActive,
|
'group relative w-full flex items-center justify-between px-2 py-2.5 rounded-lg',
|
||||||
'text-gray-500 hover:bg-gray-50 hover:text-gray-800' => ! $isActive,
|
'text-sm font-medium transition-colors duration-150 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400',
|
||||||
])
|
'bg-gray-100 text-gray-900' => $isActive,
|
||||||
aria-current="{{ $isActive ? 'page' : 'false' }}"
|
'text-gray-500 hover:bg-gray-50 hover:text-gray-800' => ! $isActive,
|
||||||
>
|
])
|
||||||
{{-- Active pill indicator --}}
|
aria-haspopup="true"
|
||||||
@if ($isActive)
|
:aria-expanded="String(expanded)"
|
||||||
<span
|
>
|
||||||
class="absolute left-0 inset-y-2 w-0.5 rounded-r-full bg-blue-600"
|
<div class="flex items-center gap-3">
|
||||||
aria-hidden="true"
|
<x-mary-icon :name="$item->icon" class="w-4 h-4 shrink-0"/>
|
||||||
></span>
|
<span
|
||||||
@endif
|
x-cloak
|
||||||
|
x-show="!collapsed"
|
||||||
|
class="whitespace-nowrap overflow-hidden leading-none"
|
||||||
|
>
|
||||||
|
{{ $item->label }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<x-mary-icon :name="$item['icon']" class="w-4 h-4"/>
|
{{-- Chevron Arrow (Hidden when collapsed) --}}
|
||||||
|
<span x-cloak x-show="!collapsed" class="shrink-0 transition-transform duration-200"
|
||||||
|
:class="expanded ? 'rotate-90' : ''">
|
||||||
|
<x-mary-icon name="lucide.chevron-right" class="w-4 h-4"/>
|
||||||
|
</span>
|
||||||
|
|
||||||
{{-- Label — hidden when collapsed --}}
|
{{-- Tooltip (Collapsed Mode) --}}
|
||||||
<span
|
<span x-cloak x-show="collapsed" role="tooltip"
|
||||||
x-cloak
|
class="pointer-events-none absolute left-full ml-3 z-50 whitespace-nowrap rounded-md bg-gray-900 px-2.5 py-1.5 text-xs font-medium text-white shadow-xl opacity-0 group-hover:opacity-100 transition-opacity duration-150 delay-75">
|
||||||
x-show="!collapsed"
|
{{ $item->label }}
|
||||||
x-transition:enter="transition-opacity duration-200 delay-100"
|
<span
|
||||||
x-transition:enter-start="opacity-0"
|
class="absolute right-full top-1/2 -translate-y-1/2 border-[5px] border-transparent border-r-gray-900"
|
||||||
x-transition:enter-end="opacity-100"
|
aria-hidden="true"></span>
|
||||||
x-transition:leave="transition-opacity duration-75"
|
</span>
|
||||||
x-transition:leave-start="opacity-100"
|
</button>
|
||||||
x-transition:leave-end="opacity-0"
|
|
||||||
class="whitespace-nowrap overflow-hidden leading-none"
|
{{-- Submenu Dropdown --}}
|
||||||
|
<div x-cloak x-show="expanded && !collapsed" x-collapse>
|
||||||
|
<div class="mt-1 space-y-1 pl-9 pr-2">
|
||||||
|
@foreach ($item->sub_menu as $subItem)
|
||||||
|
@php $isSubActive = request()->routeIs($subItem->active_pattern); @endphp
|
||||||
|
<a
|
||||||
|
href="{{ route($subItem->route) }}"
|
||||||
|
wire:navigate
|
||||||
|
@class([
|
||||||
|
'block px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
||||||
|
'bg-gray-100 text-blue-700' => $isSubActive,
|
||||||
|
'text-gray-500 hover:bg-gray-50 hover:text-gray-900' => ! $isSubActive,
|
||||||
|
])
|
||||||
|
>
|
||||||
|
{{ $subItem->label }}
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
{{-- STANDARD ITEM --}}
|
||||||
|
<a
|
||||||
|
href="{{ route($item->route) }}"
|
||||||
|
wire:navigate
|
||||||
|
@class([
|
||||||
|
'group relative flex items-center gap-3 px-2 py-2.5 rounded-lg',
|
||||||
|
'text-sm font-medium transition-colors duration-150',
|
||||||
|
'bg-gray-100 text-gray-900' => $isActive,
|
||||||
|
'text-gray-500 hover:bg-gray-50 hover:text-gray-800' => ! $isActive,
|
||||||
|
])
|
||||||
|
aria-current="{{ $isActive ? 'page' : 'false' }}"
|
||||||
>
|
>
|
||||||
{{ $item['label'] }}
|
@if ($isActive)
|
||||||
</span>
|
<span class="absolute left-0 inset-y-2 w-0.5 rounded-r-full bg-blue-600"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<x-mary-icon :name="$item->icon" class="w-4 h-4 shrink-0"/>
|
||||||
|
|
||||||
{{-- Tooltip — only rendered & visible in collapsed mode --}}
|
|
||||||
<span
|
|
||||||
x-cloak
|
|
||||||
x-show="collapsed"
|
|
||||||
role="tooltip"
|
|
||||||
class="pointer-events-none absolute left-full ml-3 z-50
|
|
||||||
whitespace-nowrap rounded-md bg-gray-900 px-2.5 py-1.5
|
|
||||||
text-xs font-medium text-white shadow-xl
|
|
||||||
opacity-0 group-hover:opacity-100
|
|
||||||
transition-opacity duration-150 delay-75"
|
|
||||||
>
|
|
||||||
{{ $item['label'] }}
|
|
||||||
{{-- Arrow --}}
|
|
||||||
<span
|
<span
|
||||||
class="absolute right-full top-1/2 -translate-y-1/2
|
x-cloak
|
||||||
border-[5px] border-transparent border-r-gray-900"
|
x-show="!collapsed"
|
||||||
aria-hidden="true"
|
class="whitespace-nowrap overflow-hidden leading-none"
|
||||||
></span>
|
>
|
||||||
</span>
|
{{ $item->label }}
|
||||||
</a>
|
</span>
|
||||||
|
|
||||||
|
{{-- Tooltip (Collapsed Mode) --}}
|
||||||
|
<span x-cloak x-show="collapsed" role="tooltip"
|
||||||
|
class="pointer-events-none absolute left-full ml-3 z-50 whitespace-nowrap rounded-md bg-gray-900 px-2.5 py-1.5 text-xs font-medium text-white shadow-xl opacity-0 group-hover:opacity-100 transition-opacity duration-150 delay-75">
|
||||||
|
{{ $item->label }}
|
||||||
|
<span
|
||||||
|
class="absolute right-full top-1/2 -translate-y-1/2 border-[5px] border-transparent border-r-gray-900"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
{{-- Toggle Footer --}}
|
||||||
<div class="border-t border-gray-100 px-2 py-3 shrink-0">
|
<div class="border-t border-gray-100 px-2 py-3 shrink-0">
|
||||||
<button
|
<button
|
||||||
wire:click="toggle"
|
wire:click="toggle"
|
||||||
@ -159,29 +231,13 @@ class="group w-full flex items-center gap-3 px-2 py-2.5 rounded-lg
|
|||||||
:aria-label="collapsed ? 'Expand sidebar' : 'Collapse sidebar'"
|
:aria-label="collapsed ? 'Expand sidebar' : 'Collapse sidebar'"
|
||||||
>
|
>
|
||||||
<span class="flex-shrink-0 flex items-center justify-center w-5 h-5" aria-hidden="true">
|
<span class="flex-shrink-0 flex items-center justify-center w-5 h-5" aria-hidden="true">
|
||||||
<svg
|
<svg :class="collapsed ? 'rotate-180' : 'rotate-0'"
|
||||||
:class="collapsed ? 'rotate-180' : 'rotate-0'"
|
class="w-4 h-4 transition-transform duration-300 ease-in-out" fill="none" viewBox="0 0 24 24"
|
||||||
class="w-4 h-4 transition-transform duration-300 ease-in-out"
|
stroke-width="2" stroke="currentColor">
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"/>
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"/>
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
|
<span x-cloak x-show="!collapsed" class="whitespace-nowrap">
|
||||||
<span
|
|
||||||
x-cloak
|
|
||||||
x-show="!collapsed"
|
|
||||||
x-transition:enter="transition-opacity duration-200 delay-100"
|
|
||||||
x-transition:enter-start="opacity-0"
|
|
||||||
x-transition:enter-end="opacity-100"
|
|
||||||
x-transition:leave="transition-opacity duration-75"
|
|
||||||
x-transition:leave-start="opacity-100"
|
|
||||||
x-transition:leave-end="opacity-0"
|
|
||||||
class="whitespace-nowrap"
|
|
||||||
>
|
|
||||||
Collapse
|
Collapse
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-shared.card title="Connected Apps" icon="lucide-grid-2x2-plus" class="p-0">
|
<x-shared.card title="Connected Apps" icon="lucide-grid-2x2-plus" class="p-0">
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-shared.button :link="route('connected-apps.create')">
|
<x-shared.button :link="route('apps.create')">
|
||||||
<div class="flex items-center gap-x-2">
|
<div class="flex items-center gap-x-2">
|
||||||
<x-lucide-plus class="w-5 h-5"/>
|
<x-lucide-plus class="w-5 h-5"/>
|
||||||
{{ __('Connect App') }}
|
{{ __('Connect App') }}
|
||||||
|
|||||||
@ -94,7 +94,7 @@ public function create()
|
|||||||
|
|
||||||
public function goBack(): void
|
public function goBack(): void
|
||||||
{
|
{
|
||||||
$this->redirectRoute('connected-apps.index', navigate: true);
|
$this->redirectRoute('apps.index', navigate: true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
?>
|
?>
|
||||||
|
|||||||
@ -44,7 +44,7 @@ public function mount(int $id): void
|
|||||||
$this->recordId = $id;
|
$this->recordId = $id;
|
||||||
},
|
},
|
||||||
onError: function () {
|
onError: function () {
|
||||||
$this->redirectRoute('connected-apps.index', navigate: true);
|
$this->redirectRoute('apps.index', navigate: true);
|
||||||
},
|
},
|
||||||
showSuccess: false
|
showSuccess: false
|
||||||
);
|
);
|
||||||
@ -98,7 +98,7 @@ public function save(): void
|
|||||||
connectionStatusId: $this->form->statusId,
|
connectionStatusId: $this->form->statusId,
|
||||||
);
|
);
|
||||||
$this->service->update($this->recordId, $serviceData);
|
$this->service->update($this->recordId, $serviceData);
|
||||||
$this->redirectRoute('connected-apps.index', navigate: true);
|
$this->redirectRoute('apps.index', navigate: true);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ public function save(): void
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<x-slot:actions class="pr-2">
|
<x-slot:actions class="pr-2">
|
||||||
<x-mary-button :link="route('connected-apps.index')" wire:navigate>
|
<x-mary-button :link="route('apps.index')" wire:navigate>
|
||||||
{{ __('Cancel') }}
|
{{ __('Cancel') }}
|
||||||
</x-mary-button>
|
</x-mary-button>
|
||||||
<x-mary-button type="submit" spinner="save" class="btn-primary">
|
<x-mary-button type="submit" spinner="save" class="btn-primary">
|
||||||
|
|||||||
@ -53,7 +53,7 @@ public function getConnectedApps(): PaginatedDataCollection
|
|||||||
|
|
||||||
public function edit(int $appId): void
|
public function edit(int $appId): void
|
||||||
{
|
{
|
||||||
$this->redirectRoute('connected-apps.edit', ['id' => $appId], navigate: true);
|
$this->redirectRoute('apps.edit', ['id' => $appId], navigate: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(int $appId): void
|
public function delete(int $appId): void
|
||||||
@ -69,7 +69,7 @@ public function delete(int $appId): void
|
|||||||
<div>
|
<div>
|
||||||
<x-shared.card title="Connected Apps" class="pb-2">
|
<x-shared.card title="Connected Apps" class="pb-2">
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-mary-button :link="route('connected-apps.create')" wire:navigate icon="lucide.plus">Add App
|
<x-mary-button :link="route('apps.create')" wire:navigate icon="lucide.plus">Add App
|
||||||
</x-mary-button>
|
</x-mary-button>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-mary-table
|
<x-mary-table
|
||||||
@ -86,9 +86,10 @@ public function delete(int $appId): void
|
|||||||
@endscope
|
@endscope
|
||||||
@scope('actions', $row)
|
@scope('actions', $row)
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<x-mary-button icon="lucide.edit" wire:click="edit({{$row->id}})" spinner="edit"
|
<x-mary-button icon="lucide.edit" wire:click="edit({{$row->id}})" spinner="edit({{$row->id}})"
|
||||||
class="mr-2 btn-sm text-gray-500"/>
|
class="mr-2 btn-sm text-gray-500"/>
|
||||||
<x-mary-button icon="lucide.trash" wire:click="delete({{$row->id}})" spinner="delete" variant="danger"
|
<x-mary-button icon="lucide.trash" wire:click="delete({{$row->id}})" spinner="delete({{$row->id}})"
|
||||||
|
variant="danger"
|
||||||
class="btn-sm btn-error btn-soft"/>
|
class="btn-sm btn-error btn-soft"/>
|
||||||
</div>
|
</div>
|
||||||
@endscope
|
@endscope
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Concerns\HandlesOperations;
|
use App\Concerns\HandlesOperations;
|
||||||
|
use App\Data\ConnectedApp\StoreConnectionProviderRequestData;
|
||||||
use App\Livewire\Forms\StoreConnectionProviderForm;
|
use App\Livewire\Forms\StoreConnectionProviderForm;
|
||||||
|
use App\Services\ConnectionProviderService;
|
||||||
use Laravel\Mcp\Server\Attributes\Title;
|
use Laravel\Mcp\Server\Attributes\Title;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Mary\Traits\Toast;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
new
|
new
|
||||||
#[Title('Create Connection Provider')]
|
#[Title('Create Connection Provider')]
|
||||||
@ -13,14 +13,25 @@ class extends Component {
|
|||||||
use HandlesOperations;
|
use HandlesOperations;
|
||||||
|
|
||||||
public StoreConnectionProviderForm $form;
|
public StoreConnectionProviderForm $form;
|
||||||
|
protected ConnectionProviderService $service;
|
||||||
|
|
||||||
|
public function boot(ConnectionProviderService $service): void
|
||||||
|
{
|
||||||
|
$this->service = $service;
|
||||||
|
}
|
||||||
|
|
||||||
public function save(): void
|
public function save(): void
|
||||||
{
|
{
|
||||||
$this->attempt(
|
$this->attempt(
|
||||||
action: function () {
|
action: function () {
|
||||||
$this->form->save();
|
$this->form->validate();
|
||||||
$this->redirectRoute('dashboard', navigate: true);
|
$data = new StoreConnectionProviderRequestData(
|
||||||
}
|
name: $this->form->name,
|
||||||
|
);
|
||||||
|
$this->service->create($data);
|
||||||
|
$this->form->reset();
|
||||||
|
$this->redirectRoute('apps.connectionProviders.index', navigate: true);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
60
resources/views/pages/connecton-providers/⚡edit.blade.php
Normal file
60
resources/views/pages/connecton-providers/⚡edit.blade.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Concerns\HandlesOperations;
|
||||||
|
use App\Data\ConnectedApp\StoreConnectionProviderRequestData;
|
||||||
|
use App\Livewire\Forms\StoreConnectionProviderForm;
|
||||||
|
use App\Services\ConnectionProviderService;
|
||||||
|
use Laravel\Mcp\Server\Attributes\Title;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Mary\Traits\Toast;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
new
|
||||||
|
#[Title('Edit Connection Provider')]
|
||||||
|
class extends Component {
|
||||||
|
use HandlesOperations;
|
||||||
|
|
||||||
|
public StoreConnectionProviderForm $form;
|
||||||
|
public string $currentProviderSlug;
|
||||||
|
|
||||||
|
protected ConnectionProviderService $service;
|
||||||
|
|
||||||
|
public function boot(ConnectionProviderService $service): void
|
||||||
|
{
|
||||||
|
$this->service = $service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mount(string $slug): void
|
||||||
|
{
|
||||||
|
$this->currentProviderSlug = $slug;
|
||||||
|
$data = $this->service->getProvider($slug);
|
||||||
|
$this->form->set($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(): void
|
||||||
|
{
|
||||||
|
$this->attempt(
|
||||||
|
action: function () {
|
||||||
|
$this->form->validate();
|
||||||
|
$data = new StoreConnectionProviderRequestData(
|
||||||
|
name: $this->form->name,
|
||||||
|
);
|
||||||
|
$this->service->update($this->currentProviderSlug, $data);
|
||||||
|
$this->redirectRoute('apps.connectionProviders.index', navigate: true);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-shared.card :title="__('Edit Connection Provider')">
|
||||||
|
<x-mary-form :no-separator="true" class="p-4" wire:submit="save">
|
||||||
|
<x-mary-input wire:model.blur="form.name" :label="__('Name')"/>
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-mary-button class="btn-primary" type="submit" spinner="save" wire:click="form.store"
|
||||||
|
:label="__('Save')"/>
|
||||||
|
</x-slot:actions>
|
||||||
|
</x-mary-form>
|
||||||
|
</x-shared.card>
|
||||||
|
</div>
|
||||||
79
resources/views/pages/connecton-providers/⚡index.blade.php
Normal file
79
resources/views/pages/connecton-providers/⚡index.blade.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Concerns\HandlesOperations;
|
||||||
|
use App\Data\Ui\TableHeader;
|
||||||
|
use App\Services\ConnectionProviderService;
|
||||||
|
use Livewire\Attributes\Computed;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Livewire\WithPagination;
|
||||||
|
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||||
|
use Spatie\LaravelData\DataCollection;
|
||||||
|
|
||||||
|
new class extends Component {
|
||||||
|
use WithPagination, HandlesOperations;
|
||||||
|
|
||||||
|
protected ConnectionProviderService $service;
|
||||||
|
|
||||||
|
#[DataCollectionOf(TableHeader::class)]
|
||||||
|
public DataCollection $headers;
|
||||||
|
|
||||||
|
public function boot(ConnectionProviderService $service): void
|
||||||
|
{
|
||||||
|
$this->service = $service;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->initTableHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function initTableHeaders(): void
|
||||||
|
{
|
||||||
|
$this->headers = TableHeader::collect([
|
||||||
|
new TableHeader(key: 'name', label: 'Name',),
|
||||||
|
], DataCollection::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed]
|
||||||
|
public function getAllProviders()
|
||||||
|
{
|
||||||
|
return $this->service->getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(string $providerSlug): void
|
||||||
|
{
|
||||||
|
$this->redirectRoute('apps.connectionProviders.edit', ['slug' => $providerSlug]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(string $providerSlug): void
|
||||||
|
{
|
||||||
|
$this->attempt(
|
||||||
|
action: fn() => $this->service->delete($providerSlug),
|
||||||
|
successMessage: "Connection Provider deleted!"
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<x-shared.card title="Connection Providers" class="pb-2">
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-mary-button :link="route('apps.connectionProviders.create')" wire:navigate icon="lucide.plus">Add
|
||||||
|
Provider
|
||||||
|
</x-mary-button>
|
||||||
|
</x-slot:actions>
|
||||||
|
<x-mary-table :headers="$headers->toArray()" :rows="$this->getAllProviders->items()" with-pagination>
|
||||||
|
@scope('actions', $row)
|
||||||
|
<div class="flex">
|
||||||
|
<x-mary-button icon="lucide.edit" wire:click="edit('{{$row->slug}}')" spinner="edit('{{$row->slug}}')"
|
||||||
|
class="mr-2 btn-sm text-gray-500"/>
|
||||||
|
<x-mary-button icon="lucide.trash" wire:click="delete('{{$row->slug}}')"
|
||||||
|
spinner="delete('{{$row->slug}}')"
|
||||||
|
variant="danger"
|
||||||
|
class="btn-sm btn-error btn-soft"/>
|
||||||
|
</div>
|
||||||
|
@endscope
|
||||||
|
</x-mary-table>
|
||||||
|
</x-shared.card>
|
||||||
|
</div>
|
||||||
@ -7,7 +7,7 @@
|
|||||||
use Laravel\Fortify\Features;
|
use Laravel\Fortify\Features;
|
||||||
|
|
||||||
Route::middleware(['auth'])->group(function (): void {
|
Route::middleware(['auth'])->group(function (): void {
|
||||||
Route::redirect('settings', 'settings/profile');
|
Route::redirect('settings', 'settings/profile')->name('settings');
|
||||||
|
|
||||||
Route::livewire('settings/profile', Profile::class)->name('profile.edit');
|
Route::livewire('settings/profile', Profile::class)->name('profile.edit');
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,14 +9,16 @@
|
|||||||
Route::group(['middleware' => ['auth', 'verified']], function (): void {
|
Route::group(['middleware' => ['auth', 'verified']], function (): void {
|
||||||
Route::livewire('dashboard', 'pages::dashboard')->name('dashboard');
|
Route::livewire('dashboard', 'pages::dashboard')->name('dashboard');
|
||||||
|
|
||||||
Route::prefix('connected-apps')->name('connected-apps.')->group(function (): void {
|
Route::prefix('apps')->name('apps.')->group(function (): void {
|
||||||
Route::livewire('create', 'pages::connected-apps.create')->name('create');
|
Route::livewire('create', 'pages::connected-apps.create')->name('create');
|
||||||
Route::livewire('{id}/edit', 'pages::connected-apps.edit')->name('edit');
|
Route::livewire('{id}/edit', 'pages::connected-apps.edit')->name('edit');
|
||||||
Route::livewire('/', 'pages::connected-apps.index')->name('index');
|
Route::livewire('/', 'pages::connected-apps.index')->name('index');
|
||||||
});
|
|
||||||
|
|
||||||
Route::prefix('connection-providers')->name('connectionProviders.')->group(function (): void {
|
Route::prefix('connection-providers')->name('connectionProviders.')->group(function (): void {
|
||||||
Route::livewire('create', 'pages::connecton-providers.create')->name('create');
|
Route::livewire('create', 'pages::connecton-providers.create')->name('create');
|
||||||
|
Route::livewire('{slug}/edit', 'pages::connecton-providers.edit')->name('edit');
|
||||||
|
Route::livewire('index', 'pages::connecton-providers.index')->name('index');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user