Client Portal Search Guide
Last Updated: 2026-01-17 Status: Implemented Plan Reference: 131-client-portal-search.md
Overview
The Client Portal Search feature provides a global search capability for client users to quickly find projects, files, invoices, conversations, and documents across their entire account. The search is accessible from the header bar and supports keyboard navigation.
Table of Contents
- Accessing Search
- Search Features
- Searchable Content
- How to Use
- Technical Architecture
- Security
- Related Features
Accessing Search
Navigation
| Access Point | Location | URL | Role |
|---|---|---|---|
| Search Bar | Header (navigation) | /portal-search (API) | Client |
Note: The search feature is only available to client users (non-admin). Admin users have separate admin search functionality.
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Ctrl+K / Cmd+K | Focus search input |
↑ / ↓ | Navigate results |
Enter | Select/navigate to result |
Escape | Close search results |
Search Features
Instant Search
- Results appear as you type (debounced 300ms)
- Minimum 2 characters required
- Up to 5 results per category
Result Grouping
Results are organized by type:
- Projects - Active and completed projects
- Files - Uploaded project files
- Invoices - All invoices
- Conversations - Message threads
- Document Requests - Pending and completed requests
Recent Searches
- Last 5 search queries stored in localStorage
- Displayed when search input is empty
- Click to re-run previous search
Result Format
Each result displays:
[Icon] Title
Subtitle (type, date, status)
Examples:
[Folder] Website Redesign
Project · Active · Due Jan 30
[File] Logo_Final_v3.png
File · Website Redesign · Uploaded Jan 15
[Invoice] INV-2026-0042
Invoice · $5,000 · Paid
[Chat] API Integration Discussion
Conversation · 3 messages · Jan 12
Searchable Content
Search Scope by Entity
| Entity | Searchable Fields |
|---|---|
| Projects | name, description |
| Files | original_filename, description |
| Invoices | number, notes, total (as string) |
| Conversations | subject |
| Document Requests | title, description |
Search Behavior
- Case-insensitive matching
- Partial matching supported
- Results filtered by user's client associations
- Non-visible files excluded (is_client_visible = false)
How to Use
Basic Search
- Click the search bar or press
Ctrl+K/Cmd+K - Type your search query (minimum 2 characters)
- Results appear grouped by type
- Click a result or use arrow keys + Enter to navigate
Filtering Results
Currently, results are filtered automatically by:
- User's associated clients only
- Active/visible items
- File visibility settings
Understanding Results
Projects:
- Shows project name and status
- Displays due date if set
- Links to project detail page
Files:
- Shows filename and parent project
- Displays upload date
- Links directly to file download or preview
Invoices:
- Shows invoice number and amount
- Displays payment status
- Links to invoice detail page
Conversations:
- Shows subject line
- Displays message count
- Links to conversation thread
Document Requests:
- Shows request title
- Displays status and due date
- Links to request detail page
Technical Architecture
Service
Location: app/Services/Portal/PortalSearchService.php
class PortalSearchService
{
public function search(User $user, string $query, int $limit = 5): SearchResults
{
$clients = $user->clients;
return new SearchResults(
projects: $this->searchProjects($clients, $query, $limit),
files: $this->searchFiles($clients, $query, $limit),
invoices: $this->searchInvoices($clients, $query, $limit),
conversations: $this->searchConversations($clients, $query, $limit),
documentRequests: $this->searchDocumentRequests($clients, $query, $limit),
);
}
}
Controller
Location: app/Http/Controllers/Portal/PortalSearchController.php
| Method | Route | Description |
|---|---|---|
__invoke() | GET /portal-search | JSON API endpoint |
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | Yes | Search query (min 2 chars) |
limit | int | No | Results per category (default 5) |
Response Format:
{
"projects": [
{"id": 1, "name": "Website Redesign", "status": "active", "url": "/portal/projects/1"}
],
"files": [...],
"invoices": [...],
"conversations": [...],
"documentRequests": [...]
}
DTO
Location: app/DataTransferObjects/SearchResults.php
class SearchResults implements JsonSerializable
{
public function __construct(
public Collection $projects,
public Collection $files,
public Collection $invoices,
public Collection $conversations,
public Collection $documentRequests,
) {}
public function jsonSerialize(): array
{
return [
'projects' => $this->projects,
'files' => $this->files,
'invoices' => $this->invoices,
'conversations' => $this->conversations,
'documentRequests' => $this->documentRequests,
];
}
}
Component
Location: resources/views/components/portal/search.blade.php
Alpine.js component with:
- Debounced input handling
- Keyboard navigation
- Recent searches in localStorage
- Result grouping and display
Routes
// In routes/portal-routes.php
Route::get('/portal-search', PortalSearchController::class)
->name('portal.search')
->middleware('auth');
Database Indexes
Migration adds performance indexes:
projects.nameinvoices.numberproject_files.original_filenameconversations.subjectdocument_requests.title
Security
Data Isolation
- Search only returns items the authenticated user has access to
- Results filtered by user's client associations (
$user->clients) - Non-visible files excluded (
is_client_visible = false)
Input Validation
- Query sanitization via Laravel validation
- Minimum 2 character query length
- Maximum limit parameter enforced
Admin Access
- Admin users return empty results (no client associations)
- Admin search uses separate admin functionality
Testing
Test Coverage
Location: tests/Feature/Portal/SearchTest.php
All 12 tests passing:
- Authentication required
- Minimum query length validation
- Returns results for each entity type
- Client data isolation (only own data)
- File visibility filtering
- Empty results handling
- Limit parameter support
- Admin access returns empty results
Running Tests
php artisan test tests/Feature/Portal/SearchTest.php
Related Features
Dependencies
| Feature | Relationship |
|---|---|
| Authentication | User login required |
| Authorization | Client data scoping |
Complementary Features
| Feature | Description |
|---|---|
| Client Dashboard | Portal home with quick access |
| Project Management | Project viewing |
| File Sharing | File access |
| Invoicing | Invoice viewing |
| Messaging | Conversation threads |
Future Enhancements
- Full-text search with relevance ranking
- Filters (date range, status, type)
- Search within file contents (PDF text extraction)
- Saved searches
- Search analytics dashboard
- Voice search on mobile
Troubleshooting
| Issue | Solution |
|---|---|
| No results appearing | Verify minimum 2 character query |
| Missing expected results | Check user-client associations |
| Files not showing | Verify is_client_visible flag |
| Slow search | Check database indexes exist |
| Keyboard shortcuts not working | Ensure focus is not in another input field |
See Also
- Client Dashboard - Portal overview
- Project Management - Project details
- File Sharing - File management