Files
timebank-cc-public/references/PROFILE_INCOMPLETE_CONFIG.md
Ronald Huynen 2547717edb Initial commit
2026-03-23 21:37:59 +01:00

28 KiB

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
  2. Configuration Structure
  3. Evaluation Logic
  4. Enforcement Locations
  5. Related Profile States
  6. Implementation Status
  7. Testing
  8. 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

'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_fieldscheck_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:

$user = User::find($id);
$isIncomplete = $user->hasIncompleteProfile($user);

Algorithm:

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:

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:

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:

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:

// 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
        });
    });
});

The application has three similar profile state systems:

1. Profile Inactive (profile_inactive)

Configuration:

'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:

'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

$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

$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

// 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

$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

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.

'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.

'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.

'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.

'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:

// 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
// 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:

$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

This is correct behavior. Admins should see all profiles.

Verify with:

$canManageProfiles = $this->getCanManageProfiles();
// Returns true for Admin/Bank profiles

Migration Guide

Adding Completion Tracking

To track when profiles become complete:

Migration:

Schema::table('users', function (Blueprint $table) {
    $table->timestamp('profile_completed_at')->nullable()->after('email_verified_at');
});

Model Method:

public function markAsComplete()
{
    if (!$this->hasIncompleteProfile($this)) {
        $this->profile_completed_at = now();
        $this->save();
    }
}

Usage:

// After profile edit
$user->markAsComplete();

See Also

  • SEARCH_REFERENCE.md - Main search implementation details
  • 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)