15 KiB
Story 6.8: System Settings
Epic Reference
Epic 6: Admin Dashboard
User Story
As an admin, I want to configure system-wide settings, So that I can customize the platform to my needs.
Dependencies
- Story 1.2: Authentication & Role System (admin auth, User model)
- Story 8.1: Email Infrastructure Setup (mail configuration for test email)
Navigation Context
- Accessible from admin dashboard sidebar/navigation
- Route:
/admin/settings - Named route:
admin.settings
Acceptance Criteria
Profile Settings
- Admin name
- Password change
- Preferred language
Email Settings
- Display current sender email from
config('mail.from.address') - Display current sender name from
config('mail.from.name') - "Send Test Email" button that sends a test email to admin's email address
- Success/error feedback after test email attempt
Notification Preferences (Future Enhancement - Not in Scope)
Note: The following are documented for future consideration but are NOT required for this story's completion. All admin notifications are currently mandatory per PRD.
- Toggle admin notifications (future)
- Summary email frequency (future)
Behavior
- Settings saved and applied immediately
- Validation for all inputs
- Flash messages for success/error states
- Password fields cleared after successful update
Technical Notes
Database Migration Required
Add preferred_language column to users table:
// database/migrations/xxxx_add_preferred_language_to_users_table.php
Schema::table('users', function (Blueprint $table) {
$table->string('preferred_language', 2)->default('ar')->after('remember_token');
});
Files to Create/Modify
| File | Action | Purpose |
|---|---|---|
resources/views/livewire/admin/settings.blade.php |
Create | Main settings Volt component |
app/Mail/TestEmail.php |
Create | Test email mailable |
resources/views/emails/test.blade.php |
Create | Test email template |
database/migrations/xxxx_add_preferred_language_to_users_table.php |
Create | Migration |
routes/web.php |
Modify | Add admin settings route |
app/Models/User.php |
Modify | Add preferred_language to fillable |
TestEmail Mailable
// app/Mail/TestEmail.php
namespace App\Mail;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
class TestEmail extends Mailable
{
public function envelope(): Envelope
{
return new Envelope(
subject: __('messages.test_email_subject'),
);
}
public function content(): Content
{
return new Content(
view: 'emails.test',
);
}
}
Volt Component Structure
<?php
// resources/views/livewire/admin/settings.blade.php
use App\Mail\TestEmail;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Validation\Rule;
use Livewire\Volt\Component;
new class extends Component {
public string $name = '';
public string $email = '';
public string $current_password = '';
public string $password = '';
public string $password_confirmation = '';
public string $preferred_language = 'ar';
public function mount(): void
{
$user = auth()->user();
$this->name = $user->name;
$this->email = $user->email;
$this->preferred_language = $user->preferred_language ?? 'ar';
}
public function updateProfile(): void
{
$this->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', Rule::unique('users')->ignore(auth()->id())],
'preferred_language' => ['required', 'in:ar,en'],
]);
auth()->user()->update([
'name' => $this->name,
'email' => $this->email,
'preferred_language' => $this->preferred_language,
]);
session()->flash('success', __('messages.profile_updated'));
}
public function updatePassword(): void
{
$this->validate([
'current_password' => ['required', 'current_password'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
auth()->user()->update([
'password' => Hash::make($this->password),
]);
$this->reset(['current_password', 'password', 'password_confirmation']);
session()->flash('success', __('messages.password_updated'));
}
public function sendTestEmail(): void
{
try {
Mail::to(auth()->user())->send(new TestEmail());
session()->flash('success', __('messages.test_email_sent'));
} catch (\Exception $e) {
session()->flash('error', __('messages.test_email_failed'));
}
}
}; ?>
<div>
{{-- UI Template Here --}}
</div>
Edge Cases & Error Handling
- Wrong current password: Validation rule
current_passwordhandles this automatically - Duplicate email:
Rule::uniquewithignore(auth()->id())prevents self-collision - Email send failure: Wrap in try/catch, show user-friendly error message
- Empty preferred_language: Default to 'ar' in mount() if null
Testing Requirements
Test File
tests/Feature/Admin/SettingsTest.php
Test Scenarios
Profile Update Tests:
test('admin can view settings page', function () {
$admin = User::factory()->create();
$this->actingAs($admin)
->get(route('admin.settings'))
->assertOk()
->assertSeeLivewire('admin.settings');
});
test('admin can update profile information', function () {
$admin = User::factory()->create();
Volt::test('admin.settings')
->actingAs($admin)
->set('name', 'Updated Name')
->set('email', 'updated@example.com')
->set('preferred_language', 'en')
->call('updateProfile')
->assertHasNoErrors();
expect($admin->fresh())
->name->toBe('Updated Name')
->email->toBe('updated@example.com')
->preferred_language->toBe('en');
});
test('profile update validates required fields', function () {
$admin = User::factory()->create();
Volt::test('admin.settings')
->actingAs($admin)
->set('name', '')
->set('email', '')
->call('updateProfile')
->assertHasErrors(['name', 'email']);
});
test('profile update prevents duplicate email', function () {
$existingUser = User::factory()->create(['email' => 'taken@example.com']);
$admin = User::factory()->create();
Volt::test('admin.settings')
->actingAs($admin)
->set('email', 'taken@example.com')
->call('updateProfile')
->assertHasErrors(['email']);
});
Password Update Tests:
test('admin can update password with correct current password', function () {
$admin = User::factory()->create([
'password' => Hash::make('old-password'),
]);
Volt::test('admin.settings')
->actingAs($admin)
->set('current_password', 'old-password')
->set('password', 'new-password')
->set('password_confirmation', 'new-password')
->call('updatePassword')
->assertHasNoErrors();
expect(Hash::check('new-password', $admin->fresh()->password))->toBeTrue();
});
test('password update fails with wrong current password', function () {
$admin = User::factory()->create([
'password' => Hash::make('correct-password'),
]);
Volt::test('admin.settings')
->actingAs($admin)
->set('current_password', 'wrong-password')
->set('password', 'new-password')
->set('password_confirmation', 'new-password')
->call('updatePassword')
->assertHasErrors(['current_password']);
});
test('password update requires confirmation match', function () {
$admin = User::factory()->create([
'password' => Hash::make('old-password'),
]);
Volt::test('admin.settings')
->actingAs($admin)
->set('current_password', 'old-password')
->set('password', 'new-password')
->set('password_confirmation', 'different-password')
->call('updatePassword')
->assertHasErrors(['password']);
});
Test Email Tests:
test('admin can send test email', function () {
Mail::fake();
$admin = User::factory()->create();
Volt::test('admin.settings')
->actingAs($admin)
->call('sendTestEmail')
->assertHasNoErrors();
Mail::assertSent(TestEmail::class, fn ($mail) =>
$mail->hasTo($admin->email)
);
});
test('test email failure shows error message', function () {
Mail::fake();
Mail::shouldReceive('to->send')->andThrow(new \Exception('SMTP error'));
$admin = User::factory()->create();
Volt::test('admin.settings')
->actingAs($admin)
->call('sendTestEmail')
->assertSessionHas('error');
});
Definition of Done
- Migration created and run for
preferred_languagecolumn - User model updated with
preferred_languagein fillable - Settings Volt component created at
resources/views/livewire/admin/settings.blade.php - TestEmail mailable created at
app/Mail/TestEmail.php - Test email template created at
resources/views/emails/test.blade.php - Route added:
Route::get('/admin/settings', ...)->name('admin.settings') - Profile update works with validation
- Password change works with current password verification
- Language preference persists across sessions
- Test email sends successfully (or shows error on failure)
- Email settings display current sender info from config
- All flash messages display correctly (success/error)
- UI follows Flux UI component patterns
- All tests pass (
php artisan test --filter=SettingsTest) - Code formatted with Pint
Estimation
Complexity: Medium | Effort: 3-4 hours
Dev Agent Record
Status
Ready for Review
Agent Model Used
Claude Opus 4.5
File List
| File | Action | Purpose |
|---|---|---|
app/Mail/TestEmail.php |
Created | Test email mailable class |
resources/views/emails/test.blade.php |
Created | Test email template |
resources/views/livewire/settings/profile.blade.php |
Modified | Added preferred_language and test email (admin-only) |
lang/en/admin.php |
Modified | Added system settings translations |
lang/ar/admin.php |
Modified | Added system settings translations (Arabic) |
lang/en/emails.php |
Modified | Added test email translations |
lang/ar/emails.php |
Modified | Added test email translations (Arabic) |
tests/Feature/Settings/ProfileUpdateTest.php |
Modified | Added preferred_language and test email tests |
Completion Notes
- Migration for
preferred_languagealready existed in base users table migration - User model already had
preferred_languagein fillable array - All tests pass covering profile update, preferred language, and test email functionality
- Used Flux UI components for consistent design
- Bilingual support (AR/EN) implemented for all user-facing strings
- Refactored: Consolidated into existing
/settings/profileinstead of creating duplicate/admin/settings
Change Log
| Date | Change | Reason |
|---|---|---|
| 2025-12-28 | Initial implementation | Story 6.8 development |
| 2025-12-28 | Refactored to use /settings/profile | QA review identified duplication with existing settings pages |
QA Results
Review Date: 2025-12-28
Reviewed By: Quinn (Test Architect)
Risk Assessment
- Risk Level: Low
- Escalation Triggers: None
- Review Depth: Standard
Code Quality Assessment
Overall Assessment: EXCELLENT
After refactoring, the implementation extends the existing /settings/profile component cleanly:
-
Volt Component (
resources/views/livewire/settings/profile.blade.php):- Extended existing component with preferred_language field
- Test email section added with
@if(auth()->user()->isAdmin())guard - Exception handling for email sending with user-friendly error messages
- No code duplication
-
TestEmail Mailable (
app/Mail/TestEmail.php):- Follows Laravel mailable conventions
- Properly accepts locale parameter for bilingual support
- Clean envelope/content separation
-
Email Template (
resources/views/emails/test.blade.php):- Proper RTL support for Arabic locale
- Uses markdown mail component
-
Translations:
- Complete bilingual support (AR/EN) in both
lang/*/admin.phpandlang/*/emails.php
- Complete bilingual support (AR/EN) in both
Requirements Traceability (Given-When-Then)
| AC | Requirement | Test Coverage | Status |
|---|---|---|---|
| Profile Settings - Name | User can update name | test profile information can be updated |
✓ |
| Profile Settings - Email | User can update email | test profile information can be updated |
✓ |
| Profile Settings - Language | User can set preferred language | test user can update preferred language, test preferred language validates allowed values |
✓ |
| Email Settings - Sender Display | Admin sees current sender from config | test admin sees email configuration section |
✓ |
| Email Settings - Test Email | Admin can send test email | test admin can send test email, test test email uses admin preferred language |
✓ |
| Access Control | Test email is admin-only | test non-admin cannot send test email |
✓ |
Refactoring Performed
ARCH-001 Resolution: Consolidated settings into existing /settings/profile
| Action | File | Change |
|---|---|---|
| Modified | resources/views/livewire/settings/profile.blade.php |
Added preferred_language and test email (admin-only) |
| Deleted | resources/views/livewire/admin/settings.blade.php |
Removed duplicate component |
| Modified | routes/web.php |
Removed admin.settings route |
| Deleted | tests/Feature/Admin/SettingsTest.php |
Removed, tests consolidated |
| Modified | tests/Feature/Settings/ProfileUpdateTest.php |
Added new feature tests |
Compliance Check
- Coding Standards: ✓ Pint passes with no issues
- Project Structure: ✓ Extends existing component, no duplication
- Testing Strategy: ✓ 12 tests in ProfileUpdateTest covering all acceptance criteria
- All ACs Met: ✓ All acceptance criteria are implemented and tested
Improvements Checklist
- Preferred language field added to profile
- Test email section (admin-only) added
- No code duplication
- All tests pass
- Bilingual support complete (AR/EN)
- Flux UI components used consistently
Security Review
- Authentication: ✓ Profile page protected by
authandactivemiddleware - Authorization: ✓ Test email method checks
isAdmin()before sending - Input Validation: ✓ All inputs properly validated
- No Security Concerns Found
Performance Considerations
- No performance issues identified
- Component is lightweight with minimal database queries
Gate Status
Gate: PASS → docs/qa/gates/6.8-system-settings.yml
Recommended Status
✓ Ready for Done - Architectural concern resolved through refactoring. All acceptance criteria met with clean, non-duplicated implementation.