Skip to content

Phase 23 — Settings Page Minimal (CONV-364–380)#295

Merged
menvil merged 18 commits into
mainfrom
release/v0.1.23-phase23-settings-page-minimal
Jun 5, 2026
Merged

Phase 23 — Settings Page Minimal (CONV-364–380)#295
menvil merged 18 commits into
mainfrom
release/v0.1.23-phase23-settings-page-minimal

Conversation

@menvil

@menvil menvil commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds /settings page with profile name editing and read-only email display
  • Implements default conversion preferences (image quality, remove metadata) stored in users.settings JSON
  • Wires user preferences into DashboardConverter via UserConverterDefaultsResolver so converter forms open with the user's saved defaults
  • Adds success/validation feedback UI in AccountSettingsForm

Scope (Phase 23 only)

This PR deliberately excludes: password change, email change, 2FA, billing settings, API key management, device/session management.

Test plan

  • Guest cannot access /settings — redirected to /login
  • Authenticated user sees current name and email on /settings
  • User can update display name (trimmed, max 255, required)
  • Email field is read-only — cannot be changed via settings form
  • User can save default image quality (medium / high / best)
  • Invalid image quality value is rejected with validation error
  • User can toggle remove metadata default on/off
  • Preferences persist in users.settings['conversion']
  • Opening a converter (e.g. PNG → JPG) pre-fills quality and remove_metadata from saved preferences
  • Success messages appear after saving profile and conversion preferences
  • Page does not contain API Keys, Invoices, Devices, or 2FA sections
  • composer test passes (695 tests)
  • composer lint passes
  • npm run build passes

🤖 Generated with Claude Code


Summary by cubic

Adds a minimal /settings page for authenticated users to edit display name and set default conversion preferences that pre-fill converter forms; email is read-only. Completes Phase 23 (CONV-364–380) with validation hardening and cleaner persistence.

  • New Features

    • Route: /settings (auth-only; guests redirected to /login)
    • Account: update name (trimmed, max 255); email shown read-only
    • Conversion defaults: image quality (medium/high/best) and remove metadata toggle; persisted to users.settings.conversion
    • Converter forms load defaults via App\Support\Converters\UserConverterDefaultsResolver
    • Success and validation feedback in the form
    • Config: converter.user_defaults and allowed_image_quality_values
  • Refactors

    • Extracted persistence into UpdateProfileNameAction and UpdateConversionPreferencesAction
    • Reject whitespace-only names via stricter validation
    • Guard invalid stored image_quality values to system default in resolver
    • Image quality options provided by the component; added tests for whitespace-only names and invalid persisted quality fallback

Written for commit 61ea415. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

Release Notes

  • New Features
    • Settings Page: Authenticated users now have access to a dedicated settings page to manage their account and conversion preferences
    • Profile Settings: Update your profile name; email is displayed as reference information
    • Conversion Preferences: Set default image quality levels (medium, high, or best) and choose whether to remove metadata from conversions by default

menvil and others added 17 commits June 5, 2026 16:44
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Email is rendered as disabled/readonly input; saveProfile() never touches
the email field, enforced by existing tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a complete user settings management system. Users can now manage their account profile and converter preferences through a dedicated settings page, with user-specific defaults automatically applied to converter options throughout the application.

Changes

User Account and Converter Preferences Settings

Layer / File(s) Summary
Converter defaults configuration and resolution
config/converter.php, app/Support/Converters/UserConverterDefaultsResolver.php, tests/Unit/Settings/UserConversionPreferencesTest.php
New configuration defines allowed image quality values (medium, high, best) and system defaults, while UserConverterDefaultsResolver extracts user-saved preferences and merges them with schema defaults for converter option initialization.
Dashboard converter user defaults integration
app/Livewire/Dashboard/DashboardConverter.php, tests/Feature/Converters/ConverterOptionsDefaultsTest.php
DashboardConverter now uses the resolver to apply user-specific converter defaults during option initialization, falling back to schema defaults when no user is authenticated.
Account settings form component implementation
app/Livewire/AccountSettingsForm.php
Livewire component with mount(), saveProfile(), and saveConversionPreferences() methods that validate and persist user name and converter preferences with appropriate success messaging and event dispatching.
Account settings form template and feature tests
resources/views/livewire/account-settings-form.blade.php, tests/Feature/Livewire/AccountSettingsFormTest.php
Two-section Blade template renders profile and preference forms with validation error display and success messages; comprehensive tests validate name updates, email read-only behavior, preference persistence, validation, and form pre-population.
Settings page view, routing, and access tests
resources/views/settings/index.blade.php, routes/web.php, tests/Feature/Settings/SettingsPageTest.php
Authenticated /settings route renders the settings page layout embedding the account settings form; access tests confirm authorization and content rendering while rejecting unauthenticated guests.

Sequence Diagram

sequenceDiagram
  participant User as Authenticated User
  participant Form as AccountSettingsForm
  participant Resolver as UserConverterDefaultsResolver
  participant Dashboard as DashboardConverter
  participant DB as User Model
  
  User->>Form: access /settings
  Form->>DB: mount() - load user data
  DB-->>Form: name, email, settings
  Form->>Form: initialize form fields with mount() data
  Form-->>User: render account-settings-form template
  
  User->>Form: update name and submit
  Form->>Form: validate name (required)
  Form->>DB: saveProfile() - persist trimmed name
  Form->>User: dispatch settings-saved (profile)
  Form-->>User: show "Profile settings saved"
  
  User->>Dashboard: navigate to converter
  Dashboard->>Resolver: initializeOptionsFromSchema()
  Resolver->>DB: get user settings.conversion
  Resolver->>Resolver: apply user preferences or config defaults
  Resolver-->>Dashboard: resolved defaults with user overrides
  Dashboard-->>User: render converter with user defaults
  
  User->>Form: update imageQuality and removeMetadata
  Form->>Form: validate imageQuality against allowed values
  Form->>Form: validate removeMetadata as boolean
  Form->>DB: saveConversionPreferences() - update settings.conversion
  Form->>User: dispatch settings-saved (conversion)
  Form-->>User: show "Conversion preferences saved"
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • menvil/FileConverter#120: Both PRs modify app/Livewire/Dashboard/DashboardConverter.php option initialization and defaults loading from schema.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change—a minimal settings page implementation for Phase 23 with issue references (CONV-364–380).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/v0.1.23-phase23-settings-page-minimal

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the release Triggers AI code review (CodeRabbit, Cubic) label Jun 5, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 11 files

Re-trigger cubic

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
tests/Feature/Converters/ConverterOptionsDefaultsTest.php (1)

52-67: ⚡ Quick win

Add a regression test for invalid stored image_quality fallback.

There’s good coverage for missing preferences, but not for malformed persisted values. Add one case asserting invalid stored quality falls back to converter.user_defaults.image_quality.

Suggested test case
+it('falls back to system image quality when stored preference is invalid', function () {
+    $user = User::factory()->create([
+        'settings' => [
+            'conversion' => [
+                'image_quality' => 'ultra',
+                'remove_metadata' => true,
+            ],
+        ],
+    ]);
+
+    $schema = [
+        ['key' => 'quality', 'type' => 'segmented', 'label' => 'Quality', 'default' => 'high', 'options' => []],
+        ['key' => 'remove_metadata', 'type' => 'toggle', 'label' => 'Remove metadata', 'default' => true],
+    ];
+
+    $defaults = app(UserConverterDefaultsResolver::class)->apply($user, $schema);
+
+    expect($defaults['quality'])->toBe(config('converter.user_defaults.image_quality'));
+    expect($defaults['remove_metadata'])->toBeTrue();
+});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Feature/Converters/ConverterOptionsDefaultsTest.php` around lines 52 -
67, Add a regression test to ensure malformed persisted image_quality falls back
to the system default: in
tests/Feature/Converters/ConverterOptionsDefaultsTest.php add a case similar to
the existing "uses system defaults when user has no conversion preferences" but
create the User with settings including an invalid 'image_quality' (e.g.
'image_quality' => 'invalid') and assert that
UserConverterDefaultsResolver::apply($user, $schema) returns quality equal to
config('converter.user_defaults.image_quality'); reference the resolver class
name UserConverterDefaultsResolver and the schema keys 'quality' and
'remove_metadata' to mirror existing test structure.
tests/Unit/Settings/UserConversionPreferencesTest.php (1)

5-18: ⚡ Quick win

Assert concrete default values, not only key presence.

Current checks can miss contract drift (e.g., wrong default quality or metadata default). Consider asserting exact values and that the configured default is included in the allowed list.

Suggested patch
 it('has default conversion preferences schema', function () {
     $defaults = config('converter.user_defaults');

-    expect($defaults)->toHaveKey('image_quality');
-    expect($defaults)->toHaveKey('remove_metadata');
+    expect($defaults['image_quality'])->toBe('high');
+    expect($defaults['remove_metadata'])->toBeTrue();
 });

 it('has allowed image quality values', function () {
     $allowed = config('converter.allowed_image_quality_values');

     expect($allowed)->toContain('medium');
     expect($allowed)->toContain('high');
     expect($allowed)->toContain('best');
+    expect($allowed)->toContain(config('converter.user_defaults.image_quality'));
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Unit/Settings/UserConversionPreferencesTest.php` around lines 5 - 18,
Update the tests to assert concrete default values instead of only key presence:
in the "has default conversion preferences schema" test, assert that
$defaults['image_quality'] equals the expected default string (e.g. 'medium' or
whatever your canonical default is) and that $defaults['remove_metadata'] equals
the expected boolean; in the "has allowed image quality values" test, assert
that the configured default image quality (from
config('converter.user_defaults.image_quality') or $defaults['image_quality'])
is contained in $allowed in addition to checking the allowed set contains
'medium', 'high', 'best'. Ensure you reference the config keys
'converter.user_defaults' and 'converter.allowed_image_quality_values' and
variables $defaults and $allowed when making these assertions.
app/Support/Converters/UserConverterDefaultsResolver.php (1)

24-49: ⚡ Quick win

Normalize persisted image_quality against allowed config values before applying defaults.

Unsupported stored values currently flow straight into quality, which can initialize options to an invalid state. Fallback to converter.user_defaults.image_quality when the stored value is not allowed.

Suggested patch
-        $imageQuality = data_get(
+        $imageQuality = data_get(
             $settings,
             'conversion.image_quality',
             config('converter.user_defaults.image_quality')
         );
+        $allowedImageQualities = config('converter.allowed_image_quality_values', []);
+        if (! is_string($imageQuality) || ! in_array($imageQuality, $allowedImageQualities, true)) {
+            $imageQuality = config('converter.user_defaults.image_quality');
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Support/Converters/UserConverterDefaultsResolver.php` around lines 24 -
49, Persisted image_quality may contain values not allowed by config and
currently gets assigned into $defaults['quality']; validate $imageQuality
against the allowed set (e.g.
config('converter.user_defaults.allowed_image_qualities') or similar constant)
after you retrieve it and if not in that allowed array replace it with the
canonical default (config('converter.user_defaults.image_quality')). Update the
code around the $imageQuality variable and before you set $defaults['quality']
so that the normalized/validated value is used; reference the $imageQuality
variable, the $defaults array, and the 'quality' schema key in
UserConverterDefaultsResolver.php when making this change.
tests/Feature/Livewire/AccountSettingsFormTest.php (1)

44-52: ⚡ Quick win

Add a whitespace-only name validation test.

Current coverage checks '' but not ' ', which is the risky path with trim-before-save behavior.

Test addition example
+it('rejects whitespace-only profile name', function () {
+    $user = User::factory()->create();
+
+    Livewire::actingAs($user)
+        ->test(AccountSettingsForm::class)
+        ->set('name', '   ')
+        ->call('saveProfile')
+        ->assertHasErrors(['name']);
+});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Feature/Livewire/AccountSettingsFormTest.php` around lines 44 - 52, Add
a new test in AccountSettingsFormTest that mirrors the existing requires profile
name test but sets the Livewire component's 'name' property to a whitespace-only
string (e.g., '   ') before calling saveProfile and assertHasErrors(['name' =>
'required']); this ensures AccountSettingsForm::class saveProfile
trimming/validation is exercised for whitespace-only input.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/Livewire/AccountSettingsForm.php`:
- Around line 45-85: The Livewire methods saveProfile and
saveConversionPreferences contain domain logic and persistence that must be
moved into Actions; create two actions (e.g., UpdateUserProfileAction and
UpdateConversionPreferencesAction) that accept the validated payload and perform
trimming, applying settings, and saving the authenticated user (including
handling settings array/keys and validation rules like Rule::in for image
quality), then update the Livewire methods to only run validation, call the
corresponding Action (inject/resolve the action and pass the validated data),
set the component messages (profileSavedMessage/preferencesSavedMessage) and
dispatch('settings-saved', ...) — remove all direct forceFill/save and settings
mutation from the Livewire component so persistence lives in app/Actions/*.
- Around line 47-55: The current validation allows whitespace-only names because
'required' accepts "   " and trim() then stores an empty string; update the
validation in AccountSettingsForm.php (the call to $this->validate() that
produces $validated) to reject whitespace-only input — e.g. add a rule for
'name' such as 'regex:/\S/' or 'not_regex:/^\s*$/' so pure-whitespace fails
validation, and keep using trim($validated['name']) when calling
$user->forceFill([...])->save() to persist the cleaned value.

In `@resources/views/livewire/account-settings-form.blade.php`:
- Around line 67-69: Replace the hardcoded <option> entries in
account-settings-form.blade.php with a loop that consumes a server-provided list
(e.g., $imageQualities or $imageQualityOptions) exposed by the Livewire
component; add a property or computed getter (e.g., imageQualities or
getImageQualityOptionsProperty) in the component class to return the allowed
value=>label pairs from the backend/config/validator, then in the Blade view
iterate over that array to render <option value="{{ $key }}">{{ $label
}}</option>; ensure the Livewire property name you choose matches the view
binding so UI and backend validation/config remain in sync.

---

Nitpick comments:
In `@app/Support/Converters/UserConverterDefaultsResolver.php`:
- Around line 24-49: Persisted image_quality may contain values not allowed by
config and currently gets assigned into $defaults['quality']; validate
$imageQuality against the allowed set (e.g.
config('converter.user_defaults.allowed_image_qualities') or similar constant)
after you retrieve it and if not in that allowed array replace it with the
canonical default (config('converter.user_defaults.image_quality')). Update the
code around the $imageQuality variable and before you set $defaults['quality']
so that the normalized/validated value is used; reference the $imageQuality
variable, the $defaults array, and the 'quality' schema key in
UserConverterDefaultsResolver.php when making this change.

In `@tests/Feature/Converters/ConverterOptionsDefaultsTest.php`:
- Around line 52-67: Add a regression test to ensure malformed persisted
image_quality falls back to the system default: in
tests/Feature/Converters/ConverterOptionsDefaultsTest.php add a case similar to
the existing "uses system defaults when user has no conversion preferences" but
create the User with settings including an invalid 'image_quality' (e.g.
'image_quality' => 'invalid') and assert that
UserConverterDefaultsResolver::apply($user, $schema) returns quality equal to
config('converter.user_defaults.image_quality'); reference the resolver class
name UserConverterDefaultsResolver and the schema keys 'quality' and
'remove_metadata' to mirror existing test structure.

In `@tests/Feature/Livewire/AccountSettingsFormTest.php`:
- Around line 44-52: Add a new test in AccountSettingsFormTest that mirrors the
existing requires profile name test but sets the Livewire component's 'name'
property to a whitespace-only string (e.g., '   ') before calling saveProfile
and assertHasErrors(['name' => 'required']); this ensures
AccountSettingsForm::class saveProfile trimming/validation is exercised for
whitespace-only input.

In `@tests/Unit/Settings/UserConversionPreferencesTest.php`:
- Around line 5-18: Update the tests to assert concrete default values instead
of only key presence: in the "has default conversion preferences schema" test,
assert that $defaults['image_quality'] equals the expected default string (e.g.
'medium' or whatever your canonical default is) and that
$defaults['remove_metadata'] equals the expected boolean; in the "has allowed
image quality values" test, assert that the configured default image quality
(from config('converter.user_defaults.image_quality') or
$defaults['image_quality']) is contained in $allowed in addition to checking the
allowed set contains 'medium', 'high', 'best'. Ensure you reference the config
keys 'converter.user_defaults' and 'converter.allowed_image_quality_values' and
variables $defaults and $allowed when making these assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 68e0f625-7982-49cb-9487-a306fa0c08fb

📥 Commits

Reviewing files that changed from the base of the PR and between fbe6cb5 and 5aa694e.

📒 Files selected for processing (11)
  • app/Livewire/AccountSettingsForm.php
  • app/Livewire/Dashboard/DashboardConverter.php
  • app/Support/Converters/UserConverterDefaultsResolver.php
  • config/converter.php
  • resources/views/livewire/account-settings-form.blade.php
  • resources/views/settings/index.blade.php
  • routes/web.php
  • tests/Feature/Converters/ConverterOptionsDefaultsTest.php
  • tests/Feature/Livewire/AccountSettingsFormTest.php
  • tests/Feature/Settings/SettingsPageTest.php
  • tests/Unit/Settings/UserConversionPreferencesTest.php

Comment thread app/Livewire/AccountSettingsForm.php
Comment thread app/Livewire/AccountSettingsForm.php Outdated
Comment thread resources/views/livewire/account-settings-form.blade.php Outdated
- Extract persistence into UpdateProfileNameAction and UpdateConversionPreferencesAction;
  Livewire component now only validates, calls action, and updates UI state
- Reject whitespace-only names with not_regex:/^\s*$/ rule (required passes "   ")
- Expose imageQualityOptions() from component; view iterates instead of hardcoding <option> tags
- Guard invalid persisted image_quality in UserConverterDefaultsResolver against config drift
- Add tests: whitespace-only name rejection, invalid persisted quality fallback,
  concrete value assertions in unit tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@menvil menvil merged commit 4846735 into main Jun 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Triggers AI code review (CodeRabbit, Cubic)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant