# PRD: Fix Failing Calendar & User Settings Test Cases

## Context

QA identified 22 failing test cases across the User Settings and Calendar Settings modules. Root causes fall into four categories: (1) email field intentionally locked as read-only, blocking edits; (2) the Mobile App notification column commented out of the UI; (3) appointment link and working hours lacking server-side validation; (4) minor UI issues (breadcrumb, cursor style, Terms of Use link, password error messaging). This PRD captures every required change to make all 22 test cases pass.

---

## Group A — User Settings / Profile (US-002, US-009, US-012, US-013, US-014, US-025, US-030, US-031, US-039, US-043, US-053)

### A1 · Fix breadcrumb (US-002)
**File:** `app/Http/Controllers/Manages/UserSettingsController.php`

After `$this->initIndex()` in both `profileSettings()` and `notificationSettings()`, override the auto-generated breadcrumb:
```php
$this->_data['breadcrumb'] = [
    route('user-settings.profile-settings') => 'User Accounts',
    '#' => 'My Profile',
];
```

### A2 · Make Email field editable (US-009, US-012, US-013)
**File:** `app/Http/Controllers/Manages/UserSettingsController.php` — `__formUiGeneration()`

Remove `'readonly' => true` and `'class' => 'form-control cursor-not-allowed'` from the `email` field definition (around line 172–175). Replace with standard required + email validation attributes.

**File:** `app/Models/User.php` — `storeProfileSettings()`

Add email validation and update before the `$data->update($input)` call:
```php
$request->validate([
    'email' => 'required|email|max:255|unique:users,email,' . $user->id . ',id,deleted_at,NULL',
]);
$input['email'] = $request->email;
```

### A3 · Add max-length validation for name & position fields (US-014)
**File:** `app/Http/Controllers/Manages/UserSettingsController.php` — `__formUiGeneration()`

Add `'maxlength' => 100` attribute to `first_name`, `last_name`; add `'maxlength' => 255` to `job_title`.

**File:** `app/Models/User.php` — `storeProfileSettings()`

Add server-side length validation:
```php
$request->validate([
    'first_name' => 'required|max:100',
    'last_name'  => 'nullable|max:100',
    'job_title'  => 'nullable|max:255',
]);
```

### A4 · Phone format validation (US-025)
**File:** `app/Models/User.php` — `storeProfileSettings()`

After phone normalization (digits only), validate minimum digit count:
```php
if (!empty($input['phone']) && strlen($input['phone']) < 10) {
    return Helper::resp('Phone number must be at least 10 digits.', 400);
}
```

### A5 · 2FA enable / disable (US-030, US-031)
The 2FA feature is implemented as a modal flow (not a checkbox). The implementation in `setup2fa()`, `confirm2fa()`, and `disable2fa()` is complete. These tests fail because:
- The test description says "checkbox" but the UI now uses dedicated "Set Up 2FA" / "Disable 2FA" buttons
- No code changes needed; test cases US-030 and US-031 must be **re-written** to test the modal button flow rather than a checkbox

Document in test suite: clicking "Set Up 2FA" → completing the TOTP/SMS modal flow enables 2FA (badge turns green). Clicking "Disable 2FA" → entering current password disables it (badge turns grey).

### A6 · Password form visibility (US-039)
The password change form (form2) already renders on the User Settings tab (`profile-settings.blade.php`, right column). The test step "Click on calendar Settings → user notifications" is incorrectly described; the password form lives on the **User Settings** tab, not Calendar Settings or Notifications.

No code change needed. Update the test case steps to: navigate to User Settings → User Settings tab → scroll to "Change Password" card on the right column. The `current_password` field (type=password) displays as blank (not pre-filled) — this is correct security practice. Update the expected result accordingly.

### A7 · Fix incorrect-password error message (US-043)
**File:** `app/Models/User.php` — `changePasswordFormPost()`

The current code throws a `ValidationException` which is then caught and re-wrapped as status 500, losing the field-specific error. Replace the throw with a direct error return:
```php
if (!FacadesHash::check($request->current_password, $user->password)) {
    return Helper::resp('The provided password does not match your current password.', 400);
}
```
Remove the `$request->validate([...])` block and replace with explicit checks that return status 400 on failure so the controller's `else` branch redirects back with the correct error flash.

### A8 · Fix Terms of Use link (US-053)
**File:** `resources/views/admin/manages/users/profile-settings.blade.php` (line 46)

Change `<a href="">See Terms of Use</a>` to:
```blade
<a href="{{ config('app.terms_url', '#') }}" target="_blank">See Terms of Use</a>
```
Add `TERMS_URL=https://...` to `.env` and `'terms_url' => env('TERMS_URL', '#')` in `config/app.php`.

---

## Group B — Notifications: Mobile App Column (US-087, US-101, US-102, US-108, US-117, US-122)

### B1 · Restore Mobile App column (US-087, US-101, US-102, US-108)
**File:** `app/Http/Controllers/Manages/UserSettingsController.php` — `__formUiGenerationForNotification()`

**Step 1 — Uncomment the heading column** (lines 609–613):
```php
"heading_mobile"  => [
    'type' => 'text_label',
    'label' => 'Mobile App: <br> <u class="h_check_uncheck" id="m_all">All</u> | <u class="h_check_uncheck" id="m_none">None</u>',
    'width' => 'col-md-2 text-center',
],
```

**Step 2 — Uncomment $mobileChecked** (line 641):
```php
$mobileChecked = isset($userNotification) && $userNotification->mobile_app_notification == 1 ? ['1'] : [];
```

**Step 3 — Uncomment per-row Mobile App checkbox** (lines 661–672):
```php
"mobile__{$item['id']}"  => [
    'type' => 'checkbox',
    'label' => '',
    'width' => 'col-md-2',
    'options' => ['1' => ''],
    'attributes' => [
        'name' => "mobile[{$item['id']}]",
        'class' => 'mobileapp_checkbox mt-4'
    ],
    'value' => $mobileChecked,
    'field_row' => 'row justify-content-center',
],
```

**Step 4 — Verify column widths** total to 12:
Label `col-md-6` + Mobile `col-md-2` + Email `col-md-2` + Popup `col-md-2` = 12 ✓

### B2 · Persist Mobile App selections on save (US-108)
**File:** `app/Models/UserNotification.php` — `store()` method

Confirm `$input['mobile']` is already processed (line 176 does this — no change needed if the form field name is `mobile[{id}]`). Verify after uncommenting that the save round-trip persists correctly.

### B3 · Responsive layout at various zoom levels (US-117)
The column structure (6+2+2+2=12) is already Bootstrap-grid compliant. At smaller viewports the columns stack naturally. Wrap the notification table row in a `table-responsive` container to prevent horizontal overflow at high zoom:

**File:** `resources/views/admin/manages/users/notification-settings.blade.php`

Wrap the `admin-form-wrapper` include for form1 in `<div class="table-responsive">...</div>`.

### B4 · Cursor pointer on All / None hover (US-122)
**File:** `app/Http/Controllers/Manages/UserSettingsController.php` — `__formUiGenerationForNotification()`

Append a `<style>` block to `include_scripts`:
```php
'include_scripts' => '<style>.h_check_uncheck { cursor: pointer; } .h_check_uncheck:hover { opacity: 0.75; }</style>
<script src="' . asset('assets/js/notification-settings.js') . '"></script>',
```

---

## Group C — Appointment Link Validation (US-129, US-131, US-132)

**File:** `app/Models/Calendar/UserCalendarSettings.php` — `calendarAppointmentLinkFormPost()`

Replace the current minimal check with full validation:
```php
public function calendarAppointmentLinkFormPost($request)
{
    $link = trim($request->input('appointment_link', ''));

    if (empty($link)) {
        return ['status' => 400, 'message' => 'Appointment link is required.'];
    }
    if (preg_match('/\s/', $link)) {
        return ['status' => 400, 'message' => 'Appointment link must not contain spaces.'];
    }
    if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $link)) {
        return ['status' => 400, 'message' => 'Appointment link may only contain letters, numbers, hyphens, and underscores.'];
    }
    if (strlen($link) > 100) {
        return ['status' => 400, 'message' => 'Appointment link must not exceed 100 characters.'];
    }

    $userProfileModel = new \App\Models\UserProfile();
    $userProfileModel->store(['appointment_link' => $link]);

    return ['status' => 200, 'message' => 'Appointment link updated successfully'];
}
```

---

## Group D — Working Hours Same-Time Validation (US-140)

**File:** `app/Models/Calendar/UserCalendarSettings.php` — `workingHoursFormPost()`

After getting `$startTime` and `$endTime` for each day, add:
```php
if ($startTime === $endTime) {
    return ['status' => 400, 'message' => "Start time and end time cannot be the same for " . $this->weekDays[$dayId]['name'] . "."];
}
if (strtotime($endTime) <= strtotime($startTime)) {
    return ['status' => 400, 'message' => "End time must be after start time for " . $this->weekDays[$dayId]['name'] . "."];
}
```

---

## Critical Files

| File | Test Cases Covered |
|------|--------------------|
| `app/Http/Controllers/Manages/UserSettingsController.php` | US-002, US-009–013, US-014, US-087, US-101, US-102, US-108, US-122 |
| `app/Models/User.php` | US-009–013, US-014, US-025, US-043 |
| `app/Models/Calendar/UserCalendarSettings.php` | US-129, US-131, US-132, US-140 |
| `resources/views/admin/manages/users/profile-settings.blade.php` | US-053 |
| `resources/views/admin/manages/users/notification-settings.blade.php` | US-117 |
| `config/app.php` + `.env` | US-053 (Terms URL) |

---

## Out of Scope — Test Case Updates Required (No Code Change)

| Test Case | Reason |
|-----------|--------|
| US-030 / US-031 | 2FA works via modal buttons; test must be re-written to test the modal flow rather than a checkbox |
| US-039 | Password form is on the User Settings tab; test navigation steps point to wrong tab — update steps only |

---

## Verification Checklist

1. **US-002** — Load `/admin/user-settings/profile`; breadcrumb reads `Home > User Accounts > My Profile`
2. **US-009/012/013** — Email field is editable; invalid format → validation error; duplicate email → unique error; valid unique email → saves
3. **US-014** — Enter 256-char name → validation error shown
4. **US-025** — Enter `abc123` or `12` in phone → server returns validation error
5. **US-043** — Submit wrong current password → error "The provided password does not match…" (not a 500)
6. **US-053** — "See Terms of Use" link has a non-empty href and opens correctly
7. **US-087** — Mobile App column visible in Notifications tab with a checkbox per row
8. **US-101/102** — Clicking "All" checks all Mobile App boxes; "None" unchecks all
9. **US-108** — Click Mobile App "None" → Save → Refresh → all Mobile App boxes remain unchecked
10. **US-122** — Hovering over All / None links shows pointer cursor
11. **US-129** — Submit ` my link ` (with spaces) → validation error
12. **US-131** — Submit `link@#$/test` → validation error
13. **US-132** — Submit 101-char link → validation error; 100-char link → saves
14. **US-140** — Select a day with identical start and end time → validation error returned
