diff --git a/app/Features/CustomMonthStartDay.php b/app/Features/CustomMonthStartDay.php new file mode 100644 index 00000000..9c1ecd46 --- /dev/null +++ b/app/Features/CustomMonthStartDay.php @@ -0,0 +1,19 @@ + true, 'calculateBalancesOnImport' => false, + 'customMonthStartDay' => false, 'transactionAnalysis' => false, ]; } $features = Feature::for($user)->values([ CalculateBalancesOnImport::class, + CustomMonthStartDay::class, TransactionAnalysis::class, ]); return [ 'cashflow' => true, 'calculateBalancesOnImport' => $features[CalculateBalancesOnImport::class] !== false, + 'customMonthStartDay' => $features[CustomMonthStartDay::class] !== false, 'transactionAnalysis' => $features[TransactionAnalysis::class] !== false, ]; } diff --git a/app/Http/Requests/Settings/ProfileUpdateRequest.php b/app/Http/Requests/Settings/ProfileUpdateRequest.php index 4d30e587..520ecfbf 100644 --- a/app/Http/Requests/Settings/ProfileUpdateRequest.php +++ b/app/Http/Requests/Settings/ProfileUpdateRequest.php @@ -2,12 +2,14 @@ namespace App\Http\Requests\Settings; +use App\Features\CustomMonthStartDay; use App\Models\User; use App\Services\CurrencyOptions; use App\Services\UserMonthPeriodService; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; +use Laravel\Pennant\Feature; class ProfileUpdateRequest extends FormRequest { @@ -20,7 +22,7 @@ class ProfileUpdateRequest extends FormRequest { $currencyOptions = app(CurrencyOptions::class); - return [ + $rules = [ 'name' => ['required', 'string', 'max:255'], 'email' => [ @@ -33,7 +35,12 @@ class ProfileUpdateRequest extends FormRequest ], 'currency_code' => ['required', 'string', 'max:3', Rule::in($currencyOptions->primaryCodes())], 'locale' => ['nullable', 'string', Rule::in(['en', 'es'])], - 'month_start_day' => ['required', 'integer', Rule::in(UserMonthPeriodService::ALLOWED_START_DAYS)], ]; + + if (Feature::for($this->user())->active(CustomMonthStartDay::class)) { + $rules['month_start_day'] = ['required', 'integer', Rule::in(UserMonthPeriodService::ALLOWED_START_DAYS)]; + } + + return $rules; } } diff --git a/resources/js/pages/settings/account.tsx b/resources/js/pages/settings/account.tsx index 01cb7c96..0e3ecb44 100644 --- a/resources/js/pages/settings/account.tsx +++ b/resources/js/pages/settings/account.tsx @@ -52,7 +52,7 @@ export default function Account({ twoFactorEnabled?: boolean; notifyOnBankTransactionsSynced?: boolean; }) { - const { auth, currencies } = usePage().props; + const { auth, currencies, features } = usePage().props; const passwordInput = useRef(null); const currentPasswordInput = useRef(null); const [notifyBankTransactions, setNotifyBankTransactions] = useState( @@ -218,53 +218,55 @@ export default function Account({ /> -
- + {features.customMonthStartDay && ( +
+ - + + + + + + {__('Calendar month (1st)')} - ))} - - + {[25, 26, 27, 28].map((day) => ( + + {__('Day :day', { + day: String(day), + })} + + ))} + + -

- {__( - 'Cashflow periods and analysis will start on this day.', - )} -

+

+ {__( + 'Cashflow periods and analysis will start on this day.', + )} +

- -
+ +
+ )} {mustVerifyEmail && auth.user.email_verified_at === null && ( diff --git a/resources/js/types/index.d.ts b/resources/js/types/index.d.ts index 28777701..796761fb 100644 --- a/resources/js/types/index.d.ts +++ b/resources/js/types/index.d.ts @@ -42,6 +42,7 @@ export interface NavDivider { export interface Features { cashflow: boolean; calculateBalancesOnImport: boolean; + customMonthStartDay: boolean; transactionAnalysis: boolean; } diff --git a/tests/Feature/Settings/ProfileUpdateTest.php b/tests/Feature/Settings/ProfileUpdateTest.php index c0614a79..3d77e908 100644 --- a/tests/Feature/Settings/ProfileUpdateTest.php +++ b/tests/Feature/Settings/ProfileUpdateTest.php @@ -1,6 +1,8 @@ create(); @@ -14,6 +16,7 @@ test('profile page is displayed', function () { test('profile information can be updated', function () { $user = User::factory()->create(); + Feature::for($user)->activate(CustomMonthStartDay::class); $response = $this ->actingAs($user) @@ -130,6 +133,7 @@ test('profile rejects bitcoin as primary currency', function () { test('profile rejects unsupported month start day', function () { $user = User::factory()->create(); + Feature::for($user)->activate(CustomMonthStartDay::class); $response = $this ->actingAs($user) @@ -143,6 +147,25 @@ test('profile rejects unsupported month start day', function () { $response->assertSessionHasErrors(['month_start_day']); }); +test('profile ignores month start day when feature is disabled', function () { + $user = User::factory()->create(['month_start_day' => 1]); + + $response = $this + ->actingAs($user) + ->patch(route('profile.update'), [ + 'name' => 'Test User', + 'email' => 'test@example.com', + 'currency_code' => 'USD', + 'month_start_day' => 25, + ]); + + $response + ->assertSessionHasNoErrors() + ->assertRedirect(route('account.edit')); + + expect($user->refresh()->month_start_day)->toBe(1); +}); + test('email verification status is unchanged when the email address is unchanged', function () { $user = User::factory()->create();