reviewd epic 7 stories
This commit is contained in:
@@ -3,40 +3,79 @@
|
||||
## Epic Reference
|
||||
**Epic 7:** Client Dashboard
|
||||
|
||||
## Dependencies
|
||||
- **Epic 2:** User Management (user model with all client profile fields must exist)
|
||||
- **Story 7.1:** Client Dashboard Overview (layout and navigation context)
|
||||
|
||||
## User Story
|
||||
As a **client**,
|
||||
I want **to view my profile information**,
|
||||
So that **I can verify my account details are correct**.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Required User Model Fields
|
||||
The `users` table must include these columns (from Epic 2):
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `user_type` | enum('individual','company') | Distinguishes client type |
|
||||
| `name` | string | Full name (individual) or company name |
|
||||
| `national_id` | string | National ID for individuals |
|
||||
| `email` | string | Email address |
|
||||
| `phone` | string | Phone number |
|
||||
| `preferred_language` | enum('ar','en') | User's language preference |
|
||||
| `company_name` | string, nullable | Company name (company clients) |
|
||||
| `company_cert_number` | string, nullable | Company registration number |
|
||||
| `contact_person_name` | string, nullable | Contact person (company clients) |
|
||||
| `contact_person_id` | string, nullable | Contact person's ID |
|
||||
| `created_at` | timestamp | Account creation date |
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### Individual Client Profile
|
||||
- [ ] Full name
|
||||
- [ ] National ID
|
||||
- [ ] Email address
|
||||
- [ ] Phone number
|
||||
- [ ] Preferred language
|
||||
- [ ] Account created date
|
||||
- [ ] Full name displayed
|
||||
- [ ] National ID displayed
|
||||
- [ ] Email address displayed
|
||||
- [ ] Phone number displayed
|
||||
- [ ] Preferred language displayed
|
||||
- [ ] Account created date displayed
|
||||
|
||||
### Company Client Profile
|
||||
- [ ] Company name
|
||||
- [ ] Registration number
|
||||
- [ ] Contact person name
|
||||
- [ ] Contact person ID
|
||||
- [ ] Email address
|
||||
- [ ] Phone number
|
||||
- [ ] Preferred language
|
||||
- [ ] Account created date
|
||||
- [ ] Company name displayed
|
||||
- [ ] Company certificate/registration number displayed
|
||||
- [ ] Contact person name displayed
|
||||
- [ ] Contact person ID displayed
|
||||
- [ ] Email address displayed
|
||||
- [ ] Phone number displayed
|
||||
- [ ] Preferred language displayed
|
||||
- [ ] Account created date displayed
|
||||
|
||||
### Features
|
||||
- [ ] Account type indicator
|
||||
- [ ] No edit capabilities (read-only)
|
||||
- [ ] Account type indicator (Individual/Company badge)
|
||||
- [ ] No edit capabilities (read-only view)
|
||||
- [ ] Message: "Contact admin to update your information"
|
||||
- [ ] Logout button
|
||||
- [ ] Logout button with confirmation redirect
|
||||
|
||||
## Technical Notes
|
||||
|
||||
### File Location
|
||||
```
|
||||
resources/views/livewire/client/profile.blade.php
|
||||
```
|
||||
|
||||
### Route
|
||||
```php
|
||||
// In routes/web.php (client authenticated routes)
|
||||
Route::get('/client/profile', \Livewire\Volt\Volt::route('client.profile'))->name('client.profile');
|
||||
```
|
||||
|
||||
### Component Implementation
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Livewire\Volt\Component;
|
||||
|
||||
new class extends Component {
|
||||
public function with(): array
|
||||
{
|
||||
@@ -58,6 +97,11 @@ new class extends Component {
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<flux:heading>{{ __('client.my_profile') }}</flux:heading>
|
||||
|
||||
<!-- Account Type Badge -->
|
||||
<flux:badge class="mt-4" variant="{{ $user->user_type === 'individual' ? 'primary' : 'secondary' }}">
|
||||
{{ $user->user_type === 'individual' ? __('profile.individual_account') : __('profile.company_account') }}
|
||||
</flux:badge>
|
||||
|
||||
<div class="bg-cream rounded-lg p-6 mt-6">
|
||||
@if($user->user_type === 'individual')
|
||||
<dl class="space-y-4">
|
||||
@@ -69,10 +113,58 @@ new class extends Component {
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.national_id') }}</dt>
|
||||
<dd>{{ $user->national_id }}</dd>
|
||||
</div>
|
||||
<!-- More fields... -->
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.email') }}</dt>
|
||||
<dd>{{ $user->email }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.phone') }}</dt>
|
||||
<dd>{{ $user->phone }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.preferred_language') }}</dt>
|
||||
<dd>{{ $user->preferred_language === 'ar' ? __('profile.arabic') : __('profile.english') }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.member_since') }}</dt>
|
||||
<dd>{{ $user->created_at->translatedFormat('F j, Y') }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
@else
|
||||
<!-- Company fields -->
|
||||
<dl class="space-y-4">
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.company_name') }}</dt>
|
||||
<dd class="text-lg font-medium">{{ $user->company_name }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.registration_number') }}</dt>
|
||||
<dd>{{ $user->company_cert_number }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.contact_person') }}</dt>
|
||||
<dd>{{ $user->contact_person_name }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.contact_person_id') }}</dt>
|
||||
<dd>{{ $user->contact_person_id }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.email') }}</dt>
|
||||
<dd>{{ $user->email }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.phone') }}</dt>
|
||||
<dd>{{ $user->phone }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.preferred_language') }}</dt>
|
||||
<dd>{{ $user->preferred_language === 'ar' ? __('profile.arabic') : __('profile.english') }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt class="text-sm text-charcoal/70">{{ __('profile.member_since') }}</dt>
|
||||
<dd>{{ $user->created_at->translatedFormat('F j, Y') }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -86,13 +178,188 @@ new class extends Component {
|
||||
</div>
|
||||
```
|
||||
|
||||
### Required Translation Keys
|
||||
|
||||
Add to `lang/en/client.php`:
|
||||
```php
|
||||
'my_profile' => 'My Profile',
|
||||
'contact_admin_to_update' => 'Contact admin to update your information',
|
||||
```
|
||||
|
||||
Add to `lang/en/profile.php`:
|
||||
```php
|
||||
'full_name' => 'Full Name',
|
||||
'national_id' => 'National ID',
|
||||
'email' => 'Email Address',
|
||||
'phone' => 'Phone Number',
|
||||
'preferred_language' => 'Preferred Language',
|
||||
'member_since' => 'Member Since',
|
||||
'company_name' => 'Company Name',
|
||||
'registration_number' => 'Registration Number',
|
||||
'contact_person' => 'Contact Person',
|
||||
'contact_person_id' => 'Contact Person ID',
|
||||
'individual_account' => 'Individual Account',
|
||||
'company_account' => 'Company Account',
|
||||
'arabic' => 'Arabic',
|
||||
'english' => 'English',
|
||||
```
|
||||
|
||||
Add to `lang/en/auth.php`:
|
||||
```php
|
||||
'logout' => 'Logout',
|
||||
```
|
||||
|
||||
Create corresponding Arabic translations in `lang/ar/` files.
|
||||
|
||||
## Test Scenarios
|
||||
|
||||
### Unit/Feature Tests
|
||||
Create `tests/Feature/Client/ProfileTest.php`:
|
||||
|
||||
```php
|
||||
use App\Models\User;
|
||||
use Livewire\Volt\Volt;
|
||||
|
||||
test('client can view individual profile page', function () {
|
||||
$user = User::factory()->individual()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->get(route('client.profile'))
|
||||
->assertOk()
|
||||
->assertSeeLivewire('client.profile');
|
||||
});
|
||||
|
||||
test('individual profile displays all required fields', function () {
|
||||
$user = User::factory()->individual()->create([
|
||||
'name' => 'Test User',
|
||||
'national_id' => '123456789',
|
||||
'email' => 'test@example.com',
|
||||
'phone' => '+970599999999',
|
||||
'preferred_language' => 'en',
|
||||
]);
|
||||
|
||||
Volt::test('client.profile')
|
||||
->actingAs($user)
|
||||
->assertSee('Test User')
|
||||
->assertSee('123456789')
|
||||
->assertSee('test@example.com')
|
||||
->assertSee('+970599999999')
|
||||
->assertSee('English');
|
||||
});
|
||||
|
||||
test('company profile displays all required fields', function () {
|
||||
$user = User::factory()->company()->create([
|
||||
'company_name' => 'Test Company',
|
||||
'company_cert_number' => 'REG-12345',
|
||||
'contact_person_name' => 'John Doe',
|
||||
'contact_person_id' => '987654321',
|
||||
'email' => 'company@example.com',
|
||||
'phone' => '+970599888888',
|
||||
'preferred_language' => 'ar',
|
||||
]);
|
||||
|
||||
Volt::test('client.profile')
|
||||
->actingAs($user)
|
||||
->assertSee('Test Company')
|
||||
->assertSee('REG-12345')
|
||||
->assertSee('John Doe')
|
||||
->assertSee('987654321')
|
||||
->assertSee('company@example.com');
|
||||
});
|
||||
|
||||
test('profile page shows correct account type badge', function () {
|
||||
$individual = User::factory()->individual()->create();
|
||||
$company = User::factory()->company()->create();
|
||||
|
||||
Volt::test('client.profile')
|
||||
->actingAs($individual)
|
||||
->assertSee(__('profile.individual_account'));
|
||||
|
||||
Volt::test('client.profile')
|
||||
->actingAs($company)
|
||||
->assertSee(__('profile.company_account'));
|
||||
});
|
||||
|
||||
test('profile page has no edit functionality', function () {
|
||||
$user = User::factory()->individual()->create();
|
||||
|
||||
Volt::test('client.profile')
|
||||
->actingAs($user)
|
||||
->assertDontSee('Edit')
|
||||
->assertDontSee('Update')
|
||||
->assertDontSee('wire:model');
|
||||
});
|
||||
|
||||
test('profile page shows contact admin message', function () {
|
||||
$user = User::factory()->individual()->create();
|
||||
|
||||
Volt::test('client.profile')
|
||||
->actingAs($user)
|
||||
->assertSee(__('client.contact_admin_to_update'));
|
||||
});
|
||||
|
||||
test('logout button logs out user and redirects to login', function () {
|
||||
$user = User::factory()->individual()->create();
|
||||
|
||||
Volt::test('client.profile')
|
||||
->actingAs($user)
|
||||
->call('logout')
|
||||
->assertRedirect(route('login'));
|
||||
|
||||
$this->assertGuest();
|
||||
});
|
||||
|
||||
test('unauthenticated users cannot access profile', function () {
|
||||
$this->get(route('client.profile'))
|
||||
->assertRedirect(route('login'));
|
||||
});
|
||||
```
|
||||
|
||||
### User Factory States Required
|
||||
Ensure `database/factories/UserFactory.php` has these states:
|
||||
|
||||
```php
|
||||
public function individual(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'user_type' => 'individual',
|
||||
'national_id' => fake()->numerify('#########'),
|
||||
'company_name' => null,
|
||||
'company_cert_number' => null,
|
||||
'contact_person_name' => null,
|
||||
'contact_person_id' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function company(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
'user_type' => 'company',
|
||||
'company_name' => fake()->company(),
|
||||
'company_cert_number' => fake()->numerify('REG-#####'),
|
||||
'contact_person_name' => fake()->name(),
|
||||
'contact_person_id' => fake()->numerify('#########'),
|
||||
'national_id' => null,
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Individual profile displays correctly
|
||||
- [ ] Company profile displays correctly
|
||||
- [ ] No edit functionality
|
||||
- [ ] Contact admin message shown
|
||||
- [ ] Logout works
|
||||
- [ ] Tests pass
|
||||
- [ ] Individual profile displays all fields correctly
|
||||
- [ ] Company profile displays all fields correctly
|
||||
- [ ] Account type badge shows correctly for both types
|
||||
- [ ] No edit functionality present (read-only)
|
||||
- [ ] Contact admin message displayed
|
||||
- [ ] Logout button works and redirects to login
|
||||
- [ ] All test scenarios pass
|
||||
- [ ] Bilingual support (AR/EN) working
|
||||
- [ ] Responsive design on mobile
|
||||
- [ ] Code formatted with Pint
|
||||
|
||||
## Estimation
|
||||
**Complexity:** Low | **Effort:** 2 hours
|
||||
**Complexity:** Low | **Effort:** 2-3 hours
|
||||
|
||||
## Notes
|
||||
- Date formatting uses `translatedFormat()` for locale-aware display
|
||||
- Ensure the User model has `$casts` for `created_at` as datetime
|
||||
- The `bg-cream` and `text-charcoal` classes should be defined in Tailwind config per project design system
|
||||
|
||||
Reference in New Issue
Block a user