Initial commit
This commit is contained in:
924
references/PROFILE_INCOMPLETE_CONFIG.md
Normal file
924
references/PROFILE_INCOMPLETE_CONFIG.md
Normal file
@@ -0,0 +1,924 @@
|
||||
# Profile Incomplete Configuration Reference
|
||||
|
||||
This document provides comprehensive documentation on the `profile_incomplete` configuration system, including how it's evaluated, enforced, and where it's used throughout the application.
|
||||
|
||||
## Table of Contents
|
||||
1. [Overview](#overview)
|
||||
2. [Configuration Structure](#configuration-structure)
|
||||
3. [Evaluation Logic](#evaluation-logic)
|
||||
4. [Enforcement Locations](#enforcement-locations)
|
||||
5. [Related Profile States](#related-profile-states)
|
||||
6. [Implementation Status](#implementation-status)
|
||||
7. [Testing](#testing)
|
||||
8. [Configuration Examples](#configuration-examples)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The `profile_incomplete` configuration system provides a mechanism to identify and optionally hide profiles that lack sufficient information. This helps ensure that only profiles with adequate content appear in search results and other discovery features.
|
||||
|
||||
### Purpose
|
||||
- Encourage users to complete their profiles with meaningful content
|
||||
- Filter incomplete profiles from search results and messaging
|
||||
- Maintain a quality threshold for publicly visible profiles
|
||||
- Allow administrators to still view and manage incomplete profiles
|
||||
|
||||
---
|
||||
|
||||
## Configuration Structure
|
||||
|
||||
### Location
|
||||
Configuration is defined in:
|
||||
- `config/timebank-default.php.example` (lines 200-208)
|
||||
- `config/timebank_cc.php.example` (lines 186-194)
|
||||
- Active config loaded via `TIMEBANK_CONFIG` environment variable
|
||||
|
||||
### Settings
|
||||
|
||||
```php
|
||||
'profile_incomplete' => [
|
||||
// Visibility settings
|
||||
'messenger_hidden' => true, // Not searchable in chat messenger
|
||||
'profile_search_hidden' => true, // Hidden from main search bar
|
||||
'profile_hidden' => true, // Profile page access control
|
||||
'profile_labeled' => true, // Show "incomplete" label on profile
|
||||
'show_warning_modal' => true, // Show warning modal when viewing own incomplete profile
|
||||
|
||||
// Completion criteria - fields
|
||||
'check_fields' => [
|
||||
'about', // Extended description field
|
||||
'about_short', // Short description field
|
||||
'motivation', // Motivation/why joining field
|
||||
'cyclos_skills', // Skills/services offered field
|
||||
],
|
||||
'check_fields_min_total_length' => 100, // Min total characters across all fields
|
||||
|
||||
// Completion criteria - relations
|
||||
'check_relations' => [
|
||||
'tags', // Skill tags
|
||||
'languages', // Spoken languages
|
||||
'locations', // Geographic locations
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
### Completion Criteria
|
||||
|
||||
A profile is considered **COMPLETE** when **ALL THREE** of these conditions are met:
|
||||
|
||||
1. ✓ At least one field from `check_fields` has data
|
||||
2. ✓ Total character length across all `check_fields` ≥ `check_fields_min_total_length` (default: 100)
|
||||
3. ✓ At least one relation from `check_relations` exists (tags, languages, OR locations)
|
||||
|
||||
If any condition fails, the profile is marked as **INCOMPLETE**.
|
||||
|
||||
---
|
||||
|
||||
## Evaluation Logic
|
||||
|
||||
### Primary Method: `hasIncompleteProfile()`
|
||||
|
||||
**Location:** `app/Traits/ProfileTrait.php:264-310`
|
||||
|
||||
**Usage:**
|
||||
```php
|
||||
$user = User::find($id);
|
||||
$isIncomplete = $user->hasIncompleteProfile($user);
|
||||
```
|
||||
|
||||
**Algorithm:**
|
||||
```php
|
||||
public function hasIncompleteProfile($model)
|
||||
{
|
||||
$config = timebank_config('profile_incomplete');
|
||||
|
||||
if (!$config) {
|
||||
return false; // No config = assume complete
|
||||
}
|
||||
|
||||
// 1. Check fields
|
||||
$hasFieldData = false;
|
||||
$totalFieldLength = 0;
|
||||
foreach ($config['check_fields'] as $field) {
|
||||
if (!empty($model->{$field})) {
|
||||
$hasFieldData = true;
|
||||
$totalFieldLength += strlen(trim($model->{$field}));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check minimum length requirement
|
||||
$meetsMinLength = $totalFieldLength >= $config['check_fields_min_total_length'];
|
||||
|
||||
// 3. Check relations
|
||||
$hasRelationData = false;
|
||||
foreach ($config['check_relations'] as $relation) {
|
||||
if (!$model->relationLoaded($relation)) {
|
||||
$model->load($relation);
|
||||
}
|
||||
if (!$model->{$relation}->isEmpty()) {
|
||||
$hasRelationData = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Complete when ALL three conditions are met
|
||||
$isComplete = $hasFieldData && $meetsMinLength && $hasRelationData;
|
||||
|
||||
// Return true if incomplete, false if complete
|
||||
return !$isComplete;
|
||||
}
|
||||
```
|
||||
|
||||
**Performance Notes:**
|
||||
- Lazy-loads relations only if not already loaded
|
||||
- Exits early on first found relation (OR logic)
|
||||
- No database queries if relations pre-loaded
|
||||
|
||||
---
|
||||
|
||||
## Enforcement Locations
|
||||
|
||||
### 1. Main Search Bar (ACTIVE)
|
||||
|
||||
**Location:** `app/Http/Livewire/MainSearchBar.php:676-684`
|
||||
|
||||
**When:** Processing profile search results
|
||||
|
||||
**Logic:**
|
||||
```php
|
||||
if (
|
||||
timebank_config('profile_incomplete.profile_search_hidden')
|
||||
&& method_exists($model, 'hasIncompleteProfile')
|
||||
&& $model->hasIncompleteProfile($model)
|
||||
) {
|
||||
return []; // Exclude from search results
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Only enforced for non-Admin/non-Bank users
|
||||
- Organizations temporarily exempted (line 681-682) for debugging
|
||||
- Filters out incomplete profiles before they reach the user
|
||||
|
||||
**Affected Models:**
|
||||
- `App\Models\User`
|
||||
- `App\Models\Organization` (currently exempted)
|
||||
- `App\Models\Bank`
|
||||
|
||||
---
|
||||
|
||||
### 2. Browse by Tag Categories (ACTIVE)
|
||||
|
||||
**Location:** `app/Http/Livewire/MainBrowseTagCategories.php:523-564`
|
||||
|
||||
**Status:** ✓ FULLY IMPLEMENTED
|
||||
|
||||
**When:** Filtering profiles during category-based search results
|
||||
|
||||
**Implementation:**
|
||||
```php
|
||||
private function filterProfile($model, bool $canManageProfiles)
|
||||
{
|
||||
if ($canManageProfiles) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
// Checks profile_inactive
|
||||
// Checks profile_email_unverified
|
||||
// Checks deleted_at
|
||||
|
||||
// Check incomplete profiles
|
||||
if (
|
||||
timebank_config('profile_incomplete.profile_search_hidden')
|
||||
&& method_exists($model, 'hasIncompleteProfile')
|
||||
&& $model->hasIncompleteProfile($model)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $model;
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Only enforced for non-Admin/non-Bank users
|
||||
- Consistent with MainSearchBar implementation
|
||||
- Filters out incomplete profiles before they reach the user
|
||||
|
||||
**Affected Models:**
|
||||
- `App\Models\User`
|
||||
- `App\Models\Organization`
|
||||
- `App\Models\Bank`
|
||||
|
||||
---
|
||||
|
||||
### 3. Profile Page Access (ACTIVE)
|
||||
|
||||
**Configuration:** `profile_incomplete.profile_hidden` and `profile_incomplete.profile_labeled`
|
||||
|
||||
**Behavior:**
|
||||
- When `profile_hidden` is `true`, non-Admin/non-Bank users cannot view the profile page
|
||||
- Returns "profile not found" view for unauthorized access
|
||||
- Admins and Banks can always view incomplete profiles
|
||||
|
||||
**Current Status:** ✓ FULLY IMPLEMENTED
|
||||
|
||||
**Location:** `app/Http/Controllers/ProfileController.php:354-407`
|
||||
|
||||
The `getActiveStates()` method checks all profile states:
|
||||
- `profile_inactive.profile_hidden` ✓
|
||||
- `profile_email_unverified.profile_hidden` ✓
|
||||
- `profile_incomplete.profile_hidden` ✓
|
||||
|
||||
**Implementation:**
|
||||
```php
|
||||
private function getActiveStates($profile)
|
||||
{
|
||||
$inactive = false;
|
||||
$hidden = false;
|
||||
$inactiveLabel = false;
|
||||
$inactiveSince = '';
|
||||
$emailUnverifiedLabel = false;
|
||||
$incompleteLabel = false;
|
||||
$removedSince = '';
|
||||
|
||||
// ... existing inactive checks ...
|
||||
|
||||
// ... existing email_unverified checks ...
|
||||
|
||||
// Check incomplete profiles
|
||||
if (method_exists($profile, 'hasIncompleteProfile') && $profile->hasIncompleteProfile($profile)) {
|
||||
if (timebank_config('profile_incomplete.profile_hidden')) {
|
||||
$hidden = true;
|
||||
}
|
||||
if (timebank_config('profile_incomplete.profile_labeled')) {
|
||||
$incompleteLabel = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ... existing deleted_at checks ...
|
||||
|
||||
return compact('inactive', 'hidden', 'inactiveLabel', 'inactiveSince',
|
||||
'emailUnverifiedLabel', 'incompleteLabel', 'removedSince');
|
||||
}
|
||||
```
|
||||
|
||||
**How it Works:**
|
||||
1. Called in `showUser()`, `showOrganization()`, and `showBank()` methods
|
||||
2. Sets `$hidden = true` when profile is incomplete and config enabled
|
||||
3. Admin override applied in calling methods (lines 88-95, 155-162, 224-231)
|
||||
4. When `$hidden && !$canManageProfiles`, returns `profile.not_found` view
|
||||
|
||||
**Affected Models:**
|
||||
- `App\Models\User`
|
||||
- `App\Models\Organization`
|
||||
- `App\Models\Bank`
|
||||
|
||||
---
|
||||
|
||||
### 4. Profile Labels (FULLY IMPLEMENTED)
|
||||
|
||||
**Configuration:** `profile_incomplete.profile_labeled`
|
||||
|
||||
**Expected Behavior:**
|
||||
- When `true`, show an "Incomplete Profile" badge/label on the profile page
|
||||
- Only visible to Admins/Banks who can view incomplete profiles
|
||||
|
||||
**Current Status:** ✓ FULLY IMPLEMENTED
|
||||
|
||||
**Backend Implementation:** ✓ COMPLETED
|
||||
- `ProfileController.php:418-425` sets `$incompleteLabel` variable
|
||||
- Value passed to views via `$states` array (lines 117, 194, 272)
|
||||
- Available in views as `$incompleteLabel`
|
||||
|
||||
**Frontend Implementation:** ✓ COMPLETED
|
||||
- `resources/views/profile/show.blade.php:18` passes variable to Livewire component
|
||||
- `resources/views/livewire/profile/show.blade.php:35-49` displays label in top section (near name)
|
||||
- `resources/views/livewire/profile/show.blade.php:263-267` displays label in activity info section
|
||||
- Label uses `text-red-700` styling to match inactive/removed profile labels and email unverified labels
|
||||
|
||||
**Configuration:**
|
||||
- Enabled in `config/timebank_cc.php:204` (`profile_labeled => true`)
|
||||
- Enabled in `config/timebank-default.php:204` (`profile_labeled => true`)
|
||||
|
||||
---
|
||||
|
||||
### 5. Incomplete Profile Warning Modal (FULLY IMPLEMENTED)
|
||||
|
||||
**Configuration:** `profile_incomplete.show_warning_modal`
|
||||
|
||||
**Expected Behavior:**
|
||||
- When `true`, show a modal dialog when user views their own incomplete profile
|
||||
- Modal explains profile is hidden and provides guidance on completing it
|
||||
- Only shown when viewing own profile (not when admins/banks view other incomplete profiles)
|
||||
- Can be dismissed by clicking close button or clicking outside modal
|
||||
|
||||
**Current Status:** ✓ FULLY IMPLEMENTED
|
||||
|
||||
**Backend Implementation:** ✓ COMPLETED
|
||||
- `ProfileController.php:115, 214, 315` sets `$showIncompleteWarning` variable based on config
|
||||
- Checks `profile_incomplete.show_warning_modal` AND profile incompleteness
|
||||
- Only triggered when viewing own profile
|
||||
- Implemented in `showUser()`, `showOrganization()`, and `showBank()` methods
|
||||
- Also implemented in `edit()` method (line 360) for edit pages
|
||||
|
||||
**Frontend Implementation:** ✓ COMPLETED
|
||||
- `resources/views/profile/show.blade.php:24-92` contains modal markup
|
||||
- `resources/views/profile-user/edit.blade.php:18-86` contains modal for edit pages
|
||||
- Uses Alpine.js for show/hide functionality
|
||||
- Includes backdrop with click-outside-to-close
|
||||
- Displays SidePost content (type: `SiteContents\ProfileIncomplete`)
|
||||
- Fallback title and description if SidePost not configured
|
||||
|
||||
**Modal Features:**
|
||||
- Warning icon with attention-grabbing styling
|
||||
- Dismissible with ESC key or close button
|
||||
- Prevents body scrolling when open
|
||||
- Smooth transitions (Alpine.js x-transition)
|
||||
- Theme-aware styling (uses theme color classes)
|
||||
|
||||
**Configuration:**
|
||||
- Enabled in `config/timebank_cc.php:208` (`show_warning_modal => true`)
|
||||
- Works independently from `profile_hidden` setting
|
||||
- Can show modal even if profile is accessible to others
|
||||
|
||||
---
|
||||
|
||||
### 6. Chat Messenger (NOT IMPLEMENTED)
|
||||
|
||||
**Configuration:** `profile_incomplete.messenger_hidden`
|
||||
|
||||
**Expected Behavior:**
|
||||
- Incomplete profiles should not appear in messenger user search
|
||||
- Cannot start new conversations with incomplete profiles
|
||||
- Existing conversations remain accessible
|
||||
|
||||
**Current Status:** ❌ NOT IMPLEMENTED
|
||||
|
||||
**Package:** `namu/wirechat` (WireChat)
|
||||
|
||||
**Location to Implement:**
|
||||
- Search participants functionality in WireChat
|
||||
- May require custom override or event listener
|
||||
|
||||
**Implementation Approach:**
|
||||
```php
|
||||
// In a custom service provider or WireChat override
|
||||
Event::listen(\Namu\WireChat\Events\SearchingParticipants::class, function ($event) {
|
||||
if (!timebank_config('profile_incomplete.messenger_hidden')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->query->where(function ($q) {
|
||||
// Filter out incomplete profiles
|
||||
$q->whereHas('user', function ($userQuery) {
|
||||
// Add logic to check profile completeness
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Profile States
|
||||
|
||||
The application has three similar profile state systems:
|
||||
|
||||
### 1. Profile Inactive (`profile_inactive`)
|
||||
|
||||
**Configuration:**
|
||||
```php
|
||||
'profile_inactive' => [
|
||||
're-activate_at_login' => true,
|
||||
'messenger_hidden' => true,
|
||||
'profile_search_hidden' => true,
|
||||
'profile_hidden' => true,
|
||||
'profile_labeled' => true,
|
||||
],
|
||||
```
|
||||
|
||||
**Evaluation:**
|
||||
- Checked via `$profile->isActive()` method
|
||||
- Based on `inactive_at` field (null or future = active, past = inactive)
|
||||
|
||||
**Enforcement:** ✓ FULLY IMPLEMENTED
|
||||
- Main search bar: Filtered ✓ (`MainSearchBar.php:654-659`)
|
||||
- Browse tags: Filtered ✓ (`MainBrowseTagCategories.php:530-536`)
|
||||
- Profile page: Hidden/Labeled ✓ (`ProfileController.php:363-374`)
|
||||
- Messenger: Hidden ✓ (assumed via WireChat)
|
||||
|
||||
---
|
||||
|
||||
### 2. Profile Email Unverified (`profile_email_unverified`)
|
||||
|
||||
**Configuration:**
|
||||
```php
|
||||
'profile_email_unverified' => [
|
||||
'messenger_hidden' => true,
|
||||
'profile_search_hidden' => true,
|
||||
'profile_hidden' => false,
|
||||
'profile_labeled' => false,
|
||||
],
|
||||
```
|
||||
|
||||
**Evaluation:**
|
||||
- Checked via `$profile->isEmailVerified()` method
|
||||
- Based on `email_verified_at` field (null or future = unverified)
|
||||
|
||||
**Enforcement:** ✓ FULLY IMPLEMENTED
|
||||
- Main search bar: Filtered ✓ (`MainSearchBar.php:661-666`)
|
||||
- Browse tags: Filtered ✓ (`MainBrowseTagCategories.php:538-544`)
|
||||
- Profile page: Hidden/Labeled ✓ (`ProfileController.php:376-384`)
|
||||
- Messenger: Hidden ✓ (assumed via WireChat)
|
||||
|
||||
---
|
||||
|
||||
### 3. Profile Incomplete (`profile_incomplete`)
|
||||
|
||||
**Configuration:** (as documented above)
|
||||
|
||||
**Evaluation:**
|
||||
- Checked via `$profile->hasIncompleteProfile($profile)` method
|
||||
- Based on field content and relations
|
||||
|
||||
**Enforcement:** ⚠️ PARTIALLY IMPLEMENTED
|
||||
- Main search bar: Filtered ✓ (`MainSearchBar.php:676-684`)
|
||||
- Browse tags: Filtered ✓ (`MainBrowseTagCategories.php:554-561`)
|
||||
- Profile page: Hidden ✓ (`ProfileController.php:387-394`)
|
||||
- Profile labels: NOT IMPLEMENTED ❌ (prepared but requires view updates)
|
||||
- Messenger: NOT IMPLEMENTED ❌
|
||||
|
||||
---
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### Summary Table
|
||||
|
||||
| Feature | Config Key | Main Search | Browse Tags | Profile Page | Labels | Warning Modal | Messenger |
|
||||
|---------|-----------|-------------|-------------|--------------|--------|---------------|-----------|
|
||||
| **Inactive** | `profile_inactive` | ✓ | ✓ | ✓ | ✓ | N/A | ✓ |
|
||||
| **Email Unverified** | `profile_email_unverified` | ✓ | ✓ | ✓ | ✓ | N/A | ✓ |
|
||||
| **Incomplete** | `profile_incomplete` | ✓ | ✓ | ✓ | ✓ | ✓ | ❌ |
|
||||
|
||||
### Priority Tasks
|
||||
|
||||
1. ~~**HIGH:** Implement incomplete profile filtering in `MainBrowseTagCategories`~~ ✓ COMPLETED
|
||||
2. ~~**HIGH:** Implement profile page hiding/access control~~ ✓ COMPLETED
|
||||
3. ~~**MEDIUM:** Implement incomplete profile labels on profile pages~~ ✓ COMPLETED
|
||||
4. ~~**MEDIUM:** Implement incomplete profile warning modal~~ ✓ COMPLETED
|
||||
5. **MEDIUM:** Implement messenger filtering
|
||||
6. ~~**LOW:** Remove temporary Organization exemption in `MainSearchBar`~~ ✓ COMPLETED
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
### Manual Testing Checklist
|
||||
|
||||
**Setup:**
|
||||
1. Create test user with minimal profile data
|
||||
2. Verify profile is incomplete: `$user->hasIncompleteProfile($user)` returns `true`
|
||||
|
||||
**Test Cases:**
|
||||
|
||||
#### Test 1: Field Content Requirements
|
||||
```php
|
||||
$user = User::factory()->create([
|
||||
'about' => '',
|
||||
'about_short' => '',
|
||||
'motivation' => '',
|
||||
'cyclos_skills' => '',
|
||||
]);
|
||||
// Add tags and locations
|
||||
$user->tags()->attach([1, 2]);
|
||||
$user->locations()->attach(1);
|
||||
|
||||
// Should be incomplete (no field data)
|
||||
assertTrue($user->hasIncompleteProfile($user));
|
||||
|
||||
// Add 50 characters
|
||||
$user->about = str_repeat('a', 50);
|
||||
$user->save();
|
||||
// Still incomplete (< 100 chars)
|
||||
assertTrue($user->hasIncompleteProfile($user));
|
||||
|
||||
// Add 50 more characters
|
||||
$user->about_short = str_repeat('b', 50);
|
||||
$user->save();
|
||||
// Now complete (100+ chars, has relations)
|
||||
assertFalse($user->hasIncompleteProfile($user));
|
||||
```
|
||||
|
||||
#### Test 2: Relation Requirements
|
||||
```php
|
||||
$user = User::factory()->create([
|
||||
'about' => str_repeat('test ', 25), // 125 characters
|
||||
]);
|
||||
// No relations yet
|
||||
|
||||
// Should be incomplete (no relations)
|
||||
assertTrue($user->hasIncompleteProfile($user));
|
||||
|
||||
// Add a tag
|
||||
$user->tags()->attach(1);
|
||||
// Now complete
|
||||
assertFalse($user->hasIncompleteProfile($user));
|
||||
```
|
||||
|
||||
#### Test 3: Search Filtering
|
||||
```php
|
||||
// Create incomplete user
|
||||
$incompleteUser = User::factory()->create(['about' => '']);
|
||||
|
||||
// Search as regular user
|
||||
actingAs($regularUser);
|
||||
$results = app(MainSearchBar::class)->search($incompleteUser->name);
|
||||
|
||||
// Should NOT find incomplete user
|
||||
assertEmpty($results);
|
||||
|
||||
// Search as admin
|
||||
actingAs($admin);
|
||||
$results = app(MainSearchBar::class)->search($incompleteUser->name);
|
||||
|
||||
// SHOULD find incomplete user (admins can see all)
|
||||
assertNotEmpty($results);
|
||||
```
|
||||
|
||||
#### Test 4: Profile Page Access
|
||||
```php
|
||||
$incompleteUser = User::factory()->create(['about' => '']);
|
||||
|
||||
// Test with profile_hidden = true
|
||||
config(['timebank-cc.profile_incomplete.profile_hidden' => true]);
|
||||
|
||||
// Access as regular user
|
||||
actingAs($regularUser);
|
||||
$response = $this->get(route('profile.show', ['type' => 'user', 'id' => $incompleteUser->id]));
|
||||
$response->assertViewIs('profile.not_found'); // Should be hidden
|
||||
|
||||
// Access as admin
|
||||
actingAs($admin);
|
||||
$response = $this->get(route('profile.show', ['type' => 'user', 'id' => $incompleteUser->id]));
|
||||
$response->assertViewIs('profile.show'); // Should be visible
|
||||
```
|
||||
|
||||
### Automated Test Suite
|
||||
|
||||
**Create:** `tests/Feature/ProfileIncompleteTest.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Locations\City;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ProfileIncompleteTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/** @test */
|
||||
public function profile_with_no_content_is_incomplete()
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'about' => '',
|
||||
'about_short' => '',
|
||||
'motivation' => '',
|
||||
'cyclos_skills' => '',
|
||||
]);
|
||||
|
||||
$this->assertTrue($user->hasIncompleteProfile($user));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function profile_with_content_but_no_relations_is_incomplete()
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'about' => str_repeat('test ', 25), // 125 chars
|
||||
]);
|
||||
|
||||
$this->assertTrue($user->hasIncompleteProfile($user));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function profile_with_short_content_and_relations_is_incomplete()
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'about' => 'Short text', // < 100 chars
|
||||
]);
|
||||
$user->tags()->attach(Tag::factory()->create()->tag_id);
|
||||
|
||||
$this->assertTrue($user->hasIncompleteProfile($user));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function profile_with_sufficient_content_and_relations_is_complete()
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'about' => str_repeat('test ', 25), // 125 chars
|
||||
]);
|
||||
$user->tags()->attach(Tag::factory()->create()->tag_id);
|
||||
|
||||
$this->assertFalse($user->hasIncompleteProfile($user));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function incomplete_profiles_hidden_from_main_search()
|
||||
{
|
||||
$this->markTestIncomplete('Implement when search filtering is active');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function admins_can_see_incomplete_profiles_in_search()
|
||||
{
|
||||
$this->markTestIncomplete('Implement when search filtering is active');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Example 1: Strict Requirements (Default)
|
||||
|
||||
Requires meaningful content across multiple fields and at least one relation.
|
||||
|
||||
```php
|
||||
'profile_incomplete' => [
|
||||
'messenger_hidden' => true,
|
||||
'profile_search_hidden' => true,
|
||||
'profile_hidden' => false,
|
||||
'profile_labeled' => false,
|
||||
'check_fields' => ['about', 'about_short', 'motivation', 'cyclos_skills'],
|
||||
'check_fields_min_total_length' => 100,
|
||||
'check_relations' => ['tags', 'languages', 'locations'],
|
||||
],
|
||||
```
|
||||
|
||||
**Use Case:** Quality-focused communities that want only well-documented profiles visible.
|
||||
|
||||
---
|
||||
|
||||
### Example 2: Minimal Requirements
|
||||
|
||||
Only requires basic information.
|
||||
|
||||
```php
|
||||
'profile_incomplete' => [
|
||||
'messenger_hidden' => false,
|
||||
'profile_search_hidden' => true,
|
||||
'profile_hidden' => false,
|
||||
'profile_labeled' => true,
|
||||
'check_fields' => ['about_short'],
|
||||
'check_fields_min_total_length' => 20,
|
||||
'check_relations' => ['locations'],
|
||||
],
|
||||
```
|
||||
|
||||
**Use Case:** Welcoming communities that want to encourage participation but still require location.
|
||||
|
||||
---
|
||||
|
||||
### Example 3: Skills-Focused Platform
|
||||
|
||||
Emphasizes skills/services offered.
|
||||
|
||||
```php
|
||||
'profile_incomplete' => [
|
||||
'messenger_hidden' => true,
|
||||
'profile_search_hidden' => true,
|
||||
'profile_hidden' => false,
|
||||
'profile_labeled' => true,
|
||||
'check_fields' => ['cyclos_skills', 'motivation'],
|
||||
'check_fields_min_total_length' => 50,
|
||||
'check_relations' => ['tags', 'locations'],
|
||||
],
|
||||
```
|
||||
|
||||
**Use Case:** Service exchange platforms where skills are the primary discovery method.
|
||||
|
||||
---
|
||||
|
||||
### Example 4: Disabled
|
||||
|
||||
Turns off incomplete profile filtering entirely.
|
||||
|
||||
```php
|
||||
'profile_incomplete' => [
|
||||
'messenger_hidden' => false,
|
||||
'profile_search_hidden' => false,
|
||||
'profile_hidden' => false,
|
||||
'profile_labeled' => false,
|
||||
'check_fields' => [],
|
||||
'check_fields_min_total_length' => 0,
|
||||
'check_relations' => [],
|
||||
],
|
||||
```
|
||||
|
||||
**Use Case:** Testing environments or very small communities where all profiles should be visible.
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Gradual Enforcement
|
||||
|
||||
Start with lenient settings and tighten over time as users complete profiles:
|
||||
|
||||
```php
|
||||
// Week 1-2: Just label incomplete profiles
|
||||
'profile_labeled' => true,
|
||||
'profile_search_hidden' => false,
|
||||
|
||||
// Week 3-4: Hide from search but allow messenger
|
||||
'profile_labeled' => true,
|
||||
'profile_search_hidden' => true,
|
||||
'messenger_hidden' => false,
|
||||
|
||||
// Week 5+: Full enforcement
|
||||
'profile_labeled' => true,
|
||||
'profile_search_hidden' => true,
|
||||
'messenger_hidden' => true,
|
||||
```
|
||||
|
||||
### 2. Clear User Communication
|
||||
|
||||
When enforcing incomplete profile restrictions:
|
||||
- Show clear messages on why their profile is hidden
|
||||
- Provide checklist of what's needed to complete profile
|
||||
- Calculate and display completion percentage
|
||||
- Send reminder emails to users with incomplete profiles
|
||||
|
||||
### 3. Admin Override
|
||||
|
||||
Always allow Admins and Banks to:
|
||||
- View all profiles regardless of completion status
|
||||
- Search for incomplete profiles
|
||||
- Message incomplete profiles
|
||||
|
||||
This is already implemented in the permission checks.
|
||||
|
||||
### 4. Performance Optimization
|
||||
|
||||
When checking many profiles:
|
||||
- Eager load relations to avoid N+1 queries
|
||||
- Cache profile completion status
|
||||
- Consider adding a `profile_completed_at` timestamp field
|
||||
|
||||
```php
|
||||
// Efficient bulk checking
|
||||
User::with(['tags', 'languages', 'locations'])
|
||||
->get()
|
||||
->filter(fn($user) => !$user->hasIncompleteProfile($user));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Profile shows as incomplete but appears complete
|
||||
|
||||
**Diagnosis:**
|
||||
```php
|
||||
$user = User::with(['tags', 'languages', 'locations'])->find($id);
|
||||
dd([
|
||||
'has_field_data' => !empty($user->about) || !empty($user->about_short),
|
||||
'field_lengths' => [
|
||||
'about' => strlen($user->about ?? ''),
|
||||
'about_short' => strlen($user->about_short ?? ''),
|
||||
'motivation' => strlen($user->motivation ?? ''),
|
||||
'cyclos_skills' => strlen($user->cyclos_skills ?? ''),
|
||||
],
|
||||
'total_length' => strlen(trim($user->about ?? '')) + strlen(trim($user->about_short ?? '')),
|
||||
'has_tags' => !$user->tags->isEmpty(),
|
||||
'has_languages' => !$user->languages->isEmpty(),
|
||||
'has_locations' => !$user->locations->isEmpty(),
|
||||
]);
|
||||
```
|
||||
|
||||
**Common Causes:**
|
||||
- Whitespace-only content in fields (use `trim()`)
|
||||
- Relations not eager-loaded (causing empty collection check to fail)
|
||||
- Total character count just under 100 threshold
|
||||
|
||||
### Issue: Admin sees incomplete profiles in search
|
||||
|
||||
**This is correct behavior.** Admins should see all profiles.
|
||||
|
||||
**Verify with:**
|
||||
```php
|
||||
$canManageProfiles = $this->getCanManageProfiles();
|
||||
// Returns true for Admin/Bank profiles
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### Adding Completion Tracking
|
||||
|
||||
To track when profiles become complete:
|
||||
|
||||
**Migration:**
|
||||
```php
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->timestamp('profile_completed_at')->nullable()->after('email_verified_at');
|
||||
});
|
||||
```
|
||||
|
||||
**Model Method:**
|
||||
```php
|
||||
public function markAsComplete()
|
||||
{
|
||||
if (!$this->hasIncompleteProfile($this)) {
|
||||
$this->profile_completed_at = now();
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```php
|
||||
// After profile edit
|
||||
$user->markAsComplete();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [SEARCH_REFERENCE.md](SEARCH_REFERENCE.md) - Main search implementation details
|
||||
- [STYLE_GUIDE.md](STYLE_GUIDE.md) - UI patterns for profile badges/labels
|
||||
- `config/timebank-default.php.example` - Full configuration reference
|
||||
- `app/Traits/ProfileTrait.php` - Core profile methods
|
||||
- `app/Traits/ActiveStatesTrait.php` - Active/inactive state management
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2025-11-03 - Bank Profile Updates
|
||||
- ✓ Added `cyclos_skills` column to banks table via migration
|
||||
- ✓ Updated `showBank()` method to include `cyclos_skills` in select statement
|
||||
- Banks now have skills field matching Users and Organizations
|
||||
|
||||
### 2025-11-03 - Search Improvements
|
||||
- ✓ Removed temporary Organization exemption from incomplete profile filtering in `MainSearchBar`
|
||||
- ✓ All profile types (Users, Organizations, Banks) now properly filtered when incomplete
|
||||
- ✓ Fixed search dropdown showing empty border when no suggestions available
|
||||
- Search behavior now consistent across all profile types
|
||||
|
||||
### 2025-11-03 - Warning Modal Implementation
|
||||
- ✓ Added `show_warning_modal` configuration setting to `profile_incomplete`
|
||||
- ✓ Implemented warning modal display when viewing own incomplete profile
|
||||
- ✓ Modal appears on both profile view and edit pages
|
||||
- ✓ Uses Alpine.js for smooth transitions and dismissal
|
||||
- ✓ Integrates with SidePost content system for customizable messaging
|
||||
- ✓ Modal checks `show_warning_modal` config (independent from `profile_hidden`)
|
||||
|
||||
### 2025-11-03 - Profile Visibility Refinements
|
||||
- ✓ Updated incomplete label visibility logic for admins/banks vs regular users
|
||||
- ✓ Regular users respect `profile_labeled` config setting
|
||||
- ✓ Admins/Banks always see incomplete labels regardless of config
|
||||
- ✓ Fixed profile access control to properly hide incomplete/inactive profiles
|
||||
- ✓ Changed incomplete label styling to `text-red-700` (matching inactive/removed)
|
||||
|
||||
### 2025-01-31 - Bank Profile Access Fix
|
||||
- ✓ Added `canViewIncompleteProfiles()` method to `ProfilePermissionTrait`
|
||||
- ✓ Banks can now view incomplete profiles (checks profile type, not permissions)
|
||||
- ✓ Maintains existing permission-based `getCanManageProfiles()` method
|
||||
- ✓ Updated all three profile show methods to use new check
|
||||
- No database migration needed - profile type check only
|
||||
|
||||
### 2025-01-31 - Profile Labels Implementation
|
||||
- ✓ Enabled `profile_incomplete.profile_labeled` in both config files
|
||||
- ✓ Enabled `profile_email_unverified.profile_labeled` in both config files
|
||||
- Frontend labels already implemented in Livewire component (lines 35-49, 263-267)
|
||||
- Incomplete profiles now show red (`text-red-700`) warning label to Admins/Banks
|
||||
- Email unverified profiles now show red (`text-red-700`) warning label to Admins/Banks
|
||||
- Labels appear in both top section (near name) and activity info section
|
||||
|
||||
### 2025-01-31 - Browse Categories & Profile Page Implementation
|
||||
- ✓ Implemented incomplete profile filtering in `MainBrowseTagCategories`
|
||||
- ✓ Implemented profile page access control in `ProfileController`
|
||||
- ✓ Added `incompleteLabel` variable to profile state system
|
||||
- Backend now fully ready for incomplete profile enforcement
|
||||
|
||||
### 2025-01-31 - Initial Documentation
|
||||
- Documented current implementation status
|
||||
- Identified missing enforcement locations
|
||||
- Created testing guidelines
|
||||
- Provided configuration examples
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-11-03
|
||||
**Maintainer:** Development Team
|
||||
**Status:** Mostly Implemented (Search ✓, Browse ✓, Profile Page Hidden ✓, Labels ✓, Warning Modal ✓ - Only Messenger Pending)
|
||||
Reference in New Issue
Block a user