From 48d2c1a47edcb59ec63adf4346285c36acb5073f Mon Sep 17 00:00:00 2001 From: = Date: Fri, 15 May 2026 09:18:40 +0000 Subject: [PATCH] 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. --- CONTRIBUTING.MD | 2 + .../ConnectedApp/ConnectionProviderData.php | 20 ++ app/Data/Ui/Sidebar/NavItemData.php | 20 ++ app/Data/Ui/Sidebar/NavSubItemData.php | 16 ++ .../Forms/StoreConnectionProviderForm.php | 18 +- app/Services/ConnectionProviderService.php | 72 +++++ .../components/dashboard-sidebar.blade.php | 248 +++++++++++------- .../connected-apps/⚡connected-apps.blade.php | 2 +- .../pages/connected-apps/⚡create.blade.php | 2 +- .../pages/connected-apps/⚡edit.blade.php | 6 +- .../pages/connected-apps/⚡index.blade.php | 9 +- .../connecton-providers/⚡create.blade.php | 21 +- .../connecton-providers/⚡edit.blade.php | 60 +++++ .../connecton-providers/⚡index.blade.php | 79 ++++++ routes/settings.php | 2 +- routes/web.php | 10 +- 16 files changed, 460 insertions(+), 127 deletions(-) create mode 100644 CONTRIBUTING.MD create mode 100644 app/Data/ConnectedApp/ConnectionProviderData.php create mode 100644 app/Data/Ui/Sidebar/NavItemData.php create mode 100644 app/Data/Ui/Sidebar/NavSubItemData.php create mode 100644 resources/views/pages/connecton-providers/⚡edit.blade.php create mode 100644 resources/views/pages/connecton-providers/⚡index.blade.php diff --git a/CONTRIBUTING.MD b/CONTRIBUTING.MD new file mode 100644 index 0000000..057931c --- /dev/null +++ b/CONTRIBUTING.MD @@ -0,0 +1,2 @@ +Always use `#[NoDiscard]` attribute in function which fetches data from DB. This ensures that +db does not called unnecessarily. diff --git a/app/Data/ConnectedApp/ConnectionProviderData.php b/app/Data/ConnectedApp/ConnectionProviderData.php new file mode 100644 index 0000000..5b3144d --- /dev/null +++ b/app/Data/ConnectedApp/ConnectionProviderData.php @@ -0,0 +1,20 @@ +name = $data->name; + } + public function rules(): array { 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); - } } diff --git a/app/Services/ConnectionProviderService.php b/app/Services/ConnectionProviderService.php index 3e7412a..e10f1c5 100644 --- a/app/Services/ConnectionProviderService.php +++ b/app/Services/ConnectionProviderService.php @@ -4,14 +4,22 @@ namespace App\Services; +use App\Data\ConnectedApp\{ConnectionProviderData, StoreConnectionProviderRequestData}; use App\Models\ConnectionProvider; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; +use InvalidArgumentException; +use NoDiscard; +use Spatie\LaravelData\PaginatedDataCollection; +use Throwable; class ConnectionProviderService { /** * @return Collection */ + #[NoDiscard] public function search(string $value): Collection { return ConnectionProvider::query() @@ -19,4 +27,68 @@ public function search(string $value): Collection ->orWhereLike('name', "%{$value}%") ->get(); } + + /** + * @throws Throwable + */ + public function create(StoreConnectionProviderRequestData $data): void + { + DB::transaction( + function () use ($data): void { + ConnectionProvider::query()->create($data->toArray()); + } + ); + } + + /** + * @return PaginatedDataCollection + */ + #[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 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); + } } diff --git a/resources/views/components/dashboard-sidebar.blade.php b/resources/views/components/dashboard-sidebar.blade.php index 7238a1b..721681c 100644 --- a/resources/views/components/dashboard-sidebar.blade.php +++ b/resources/views/components/dashboard-sidebar.blade.php @@ -1,6 +1,9 @@ $this->collapsed]); } - public function navItems(): array + /** + * @return DataCollection + */ + public function navItems(): DataCollection { - return [ - [ - 'label' => 'Dashboard', - 'icon' => 'lucide.house', - 'route' => 'dashboard', - 'active_pattern' => 'dashboard', - ], - [ - 'label' => 'Users', - 'icon' => 'lucide.users', - 'route' => 'home', - 'active_pattern' => 'home', - ], - [ - 'label' => 'Apps', - 'icon' => 'lucide.grid-2x2-plus', - 'route' => 'connected-apps.index', - // Using a wildcard ensures the tab stays active on nested routes like connected-apps.edit - 'active_pattern' => 'connected-apps.*', - ], - ]; + return NavItemData::collect([ + new NavItemData( + label: 'Dashboard', + icon: 'lucide.house', + active_pattern: 'dashboard', + route: 'dashboard', + ), + + new NavItemData( + label: 'Apps', + icon: 'lucide.grid-2x2-plus', + active_pattern: 'apps.*', + sub_menu: NavSubItemData::collect([ + new NavSubItemData( + label: 'All Apps', + route: 'apps.index', + active_pattern: 'apps.index' + ), + 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 + {{-- Toggle Footer --}}
diff --git a/resources/views/components/dashboard/connected-apps/⚡connected-apps.blade.php b/resources/views/components/dashboard/connected-apps/⚡connected-apps.blade.php index f81059e..f2b2ac7 100644 --- a/resources/views/components/dashboard/connected-apps/⚡connected-apps.blade.php +++ b/resources/views/components/dashboard/connected-apps/⚡connected-apps.blade.php @@ -10,7 +10,7 @@
- +
{{ __('Connect App') }} diff --git a/resources/views/pages/connected-apps/⚡create.blade.php b/resources/views/pages/connected-apps/⚡create.blade.php index fc096aa..4e20277 100644 --- a/resources/views/pages/connected-apps/⚡create.blade.php +++ b/resources/views/pages/connected-apps/⚡create.blade.php @@ -94,7 +94,7 @@ public function create() public function goBack(): void { - $this->redirectRoute('connected-apps.index', navigate: true); + $this->redirectRoute('apps.index', navigate: true); } }; ?> diff --git a/resources/views/pages/connected-apps/⚡edit.blade.php b/resources/views/pages/connected-apps/⚡edit.blade.php index 026824f..f3e6224 100644 --- a/resources/views/pages/connected-apps/⚡edit.blade.php +++ b/resources/views/pages/connected-apps/⚡edit.blade.php @@ -44,7 +44,7 @@ public function mount(int $id): void $this->recordId = $id; }, onError: function () { - $this->redirectRoute('connected-apps.index', navigate: true); + $this->redirectRoute('apps.index', navigate: true); }, showSuccess: false ); @@ -98,7 +98,7 @@ public function save(): void connectionStatusId: $this->form->statusId, ); $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 />
- + {{ __('Cancel') }} diff --git a/resources/views/pages/connected-apps/⚡index.blade.php b/resources/views/pages/connected-apps/⚡index.blade.php index 4a1d055..1093a29 100644 --- a/resources/views/pages/connected-apps/⚡index.blade.php +++ b/resources/views/pages/connected-apps/⚡index.blade.php @@ -53,7 +53,7 @@ public function getConnectedApps(): PaginatedDataCollection 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 @@ -69,7 +69,7 @@ public function delete(int $appId): void
- Add App + Add App - -
@endscope diff --git a/resources/views/pages/connecton-providers/⚡create.blade.php b/resources/views/pages/connecton-providers/⚡create.blade.php index 46b7018..94a6a9a 100644 --- a/resources/views/pages/connecton-providers/⚡create.blade.php +++ b/resources/views/pages/connecton-providers/⚡create.blade.php @@ -1,11 +1,11 @@ service = $service; + } public function save(): void { $this->attempt( action: function () { - $this->form->save(); - $this->redirectRoute('dashboard', navigate: true); - } + $this->form->validate(); + $data = new StoreConnectionProviderRequestData( + name: $this->form->name, + ); + $this->service->create($data); + $this->form->reset(); + $this->redirectRoute('apps.connectionProviders.index', navigate: true); + }, ); } }; diff --git a/resources/views/pages/connecton-providers/⚡edit.blade.php b/resources/views/pages/connecton-providers/⚡edit.blade.php new file mode 100644 index 0000000..3f0e286 --- /dev/null +++ b/resources/views/pages/connecton-providers/⚡edit.blade.php @@ -0,0 +1,60 @@ +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); + }, + ); + } +}; +?> + +
+ + + + + + + + +
diff --git a/resources/views/pages/connecton-providers/⚡index.blade.php b/resources/views/pages/connecton-providers/⚡index.blade.php new file mode 100644 index 0000000..5a6d30d --- /dev/null +++ b/resources/views/pages/connecton-providers/⚡index.blade.php @@ -0,0 +1,79 @@ +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!" + ); + + } +}; +?> + +
+ + + Add + Provider + + + + @scope('actions', $row) +
+ + +
+ @endscope +
+
+
diff --git a/routes/settings.php b/routes/settings.php index 208a02c..3b17b10 100644 --- a/routes/settings.php +++ b/routes/settings.php @@ -7,7 +7,7 @@ use Laravel\Fortify\Features; 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'); }); diff --git a/routes/web.php b/routes/web.php index c262f97..51df829 100644 --- a/routes/web.php +++ b/routes/web.php @@ -9,14 +9,16 @@ Route::group(['middleware' => ['auth', 'verified']], function (): void { 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('{id}/edit', 'pages::connected-apps.edit')->name('edit'); Route::livewire('/', 'pages::connected-apps.index')->name('index'); - }); - Route::prefix('connection-providers')->name('connectionProviders.')->group(function (): void { - Route::livewire('create', 'pages::connecton-providers.create')->name('create'); + Route::prefix('connection-providers')->name('connectionProviders.')->group(function (): void { + 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'); + }); }); });