925 lines
28 KiB
Markdown
925 lines
28 KiB
Markdown
# 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)
|