From 37ac4fc994ec6b2115f96fddeeea400deecb9a61 Mon Sep 17 00:00:00 2001 From: elnuage Date: Wed, 17 Jun 2026 10:24:38 +0200 Subject: [PATCH] Add support for personal subscription --- .../PersonalSubscriptionController.php | 44 +++++++++++++++++++ app/Models/PersonalSubscription.php | 22 ++++++++++ app/Models/User.php | 5 +++ ...55_create_personal_subscriptions_table.php | 34 ++++++++++++++ resources/js/providers/menu-item-provider.tsx | 9 ++++ routes/web.php | 6 +++ 6 files changed, 120 insertions(+) create mode 100644 app/Http/Controllers/PersonalSubscriptionController.php create mode 100644 app/Models/PersonalSubscription.php create mode 100644 database/migrations/2026_06_16_210355_create_personal_subscriptions_table.php diff --git a/app/Http/Controllers/PersonalSubscriptionController.php b/app/Http/Controllers/PersonalSubscriptionController.php new file mode 100644 index 00000000..d69ec056 --- /dev/null +++ b/app/Http/Controllers/PersonalSubscriptionController.php @@ -0,0 +1,44 @@ + $request->user() + ->personalSubscriptions() + ->orderBy('next_billing_date') + ->get(), + ]); + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'amount' => ['required', 'numeric', 'min:0'], + 'currency' => ['required', 'string', 'size:3'], + 'billing_cycle' => ['required', 'in:weekly,monthly,biweekly,yearly'], + 'next_billing_date' => ['required', 'date'], + 'color' => ['nullable', 'string', 'max:20'], + ]); + + $request->user()->personalSubscriptions()->create($validated); + + return back(); + } + + public function destroy(Request $request, PersonalSubscription $personalSubscription) + { + abort_unless($personalSubscription->user_id === $request->user()->id, 403); + $personalSubscription->delete(); + + return back(); + } +} \ No newline at end of file diff --git a/app/Models/PersonalSubscription.php b/app/Models/PersonalSubscription.php new file mode 100644 index 00000000..f43c49f4 --- /dev/null +++ b/app/Models/PersonalSubscription.php @@ -0,0 +1,22 @@ + 'date', + 'amount' => 'decimal:2', + ]; + + public function user() + { + return $this->belongsTo(User::class); + } +} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 98b9914c..131e87cb 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -368,4 +368,9 @@ class User extends Authenticatable implements HasLocalePreference, MustVerifyEma return "{$timestamp}_{$this->getKey()}_{$originalEmail}"; } + + public function personalSubscriptions() + { + return $this->hasMany(PersonalSubscription::class); + } } diff --git a/database/migrations/2026_06_16_210355_create_personal_subscriptions_table.php b/database/migrations/2026_06_16_210355_create_personal_subscriptions_table.php new file mode 100644 index 00000000..4068986c --- /dev/null +++ b/database/migrations/2026_06_16_210355_create_personal_subscriptions_table.php @@ -0,0 +1,34 @@ +id(); + $table->foreignUuid('user_id')->constrained()->cascadeOnDelete(); + $table->string('name'); + $table->decimal('amount', 8, 2); + $table->string('currency', 3)->default('EUR'); + $table->enum('billing_cycle', ['weekly', 'monthly', 'biweekly', 'yearly'])->default('monthly'); + $table->date('next_billing_date'); + $table->string('color')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('personal_subscriptions'); + } +}; diff --git a/resources/js/providers/menu-item-provider.tsx b/resources/js/providers/menu-item-provider.tsx index 44ebf3d7..dfdf58c9 100644 --- a/resources/js/providers/menu-item-provider.tsx +++ b/resources/js/providers/menu-item-provider.tsx @@ -1,9 +1,11 @@ import { index as accountsIndex } from '@/actions/App/Http/Controllers/AccountController'; import { index as budgetsIndex } from '@/actions/App/Http/Controllers/BudgetController'; import { index as transactionsIndex } from '@/actions/App/Http/Controllers/TransactionController'; +import { index as PersonalSubscriptionIndex } from '@/actions/App/Http/Controllers/PersonalSubscriptionController'; import { cashflow, dashboard } from '@/routes'; import { Features, NavItem } from '@/types'; import { + CalendarSync, CreditCard, LayoutGrid, PiggyBank, @@ -82,6 +84,13 @@ export function getMainNavItems(features: Features, locale: string): NavItem[] { href: budgetsIndex(), icon: PiggyBank, }, + { + type: 'nav-item', + title: 'Subscriptions', + mobileTitle: getMobileLabel('budgets', locale), + href: PersonalSubscriptionIndex(), + icon: CalendarSync, + }, ); return items; diff --git a/routes/web.php b/routes/web.php index f38f7abb..a904b39b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -23,6 +23,7 @@ use App\Http\Controllers\SitemapController; use App\Http\Controllers\SubscriptionController; use App\Http\Controllers\TransactionController; use App\Http\Controllers\UserLeadController; +use App\Http\Controllers\PersonalSubscriptionController; use App\Models\Bank; use App\Services\LandingAuthOverrideService; use Illuminate\Http\Request; @@ -168,4 +169,9 @@ Route::middleware(['auth', 'verified', 'onboarded', 'subscribed'])->group(functi Route::delete('budgets/{budget}', [BudgetController::class, 'destroy'])->name('budgets.destroy'); }); +Route::middleware(['auth'])->group(function () { + Route::resource('personal-subscriptions', PersonalSubscriptionController::class) + ->only(['index', 'store', 'destroy']); +}); + require __DIR__.'/settings.php';