Explore Components
Der Explore-Bereich umfasst sechs Components fuer die oeffentliche Profil-Entdeckung.
Explore/Index
Startseite mit kuratierten Sektionen: Trending, Fastest Growing, Rising Stars, Top Scores, Score Breakouts, Neue Profile, Trending Videos und Top Performing Videos.
Route: /explore
View: resources/views/livewire/explore/index.blade.php
Location: app/Livewire/Explore/Index.php
Public Properties
| Property | Typ | Default | URL-Param | Beschreibung |
|---|---|---|---|---|
$platform | string | 'all' | ja | Plattform-Filter |
$country | string | 'all' | ja | Laender-Filter (ISO 3166-1 alpha-2) |
$category | string | 'all' | ja | Kategorie-Filter (ExploreCategory) |
$tier | string | 'all' | ja | Follower-Tier: micro/small/medium/large/mega |
$verified | string | 'all' | ja | Verifizierungs-Filter: verified/unverified |
$sort | string | 'trending' | ja | Sortierung (nur fuer Browse) |
$perPage | int | 24 | nein | Karten pro Sektion |
$addedProfileIds | array | [] | nein | profileId => watcherId Mapping |
Computed Properties
| Property | Cache | Beschreibung |
|---|---|---|
trendingProfiles | nein | Top 12 nach trending_score |
fastestGrowing | nein | Top 12 nach growth_score |
risingStars | nein | Top 12 mit is_rising_star = true |
topScores | nein | Top 12 nach social_profile_scores.score |
scoreBreakouts | nein | Profile die in den letzten 7 Tagen Score >= 70 erreicht haben |
newProfiles | nein | Top 12 mit is_new = true |
trendingVideos | nein | Top 8 nach trending_score (ExploreTrendingVideo) |
topPerformingVideos | nein | Top 8 nach Video Performance Score (YouTubeVideoScore) |
availableCountries | 24h | Laender mit >= 10 Profilen |
availableCategories | 24h | Aktive Kategorien mit > 0 Profilen |
stats | 24h | Gesamtstatistik (total, trending, rising_stars, countries) |
Actions
| Method | Beschreibung |
|---|---|
addToWorkspace(int $socialProfileId) | Profil als Watcher zum ersten regulaeren Workspace hinzufuegen |
resetFilters() | Alle Filter zuruecksetzen |
loadAddedProfileIds(array $ids) | Pruefen welche Profile bereits getrackt werden |
Base Query
Die getProfilesQuery() baut die Standard-Query mit:
- JOIN auf
explore_profile_metrics - Ausschluss-Filter:
blocked_at,sanitized_atjeweilsIS NULL - Minimum-Follower-Filter:
ExploreProfileMetric::MIN_FOLLOWERS_FOR_EXPLORE - Subquery fuer
avg_video_score(letzten 7 Tage) - Subquery fuer
approved_contact_count(genehmigte Links)
Explore/Browse
Paginierter Profil-Browser mit erweiterten Filtern (zusaetzlich zu Index: Tag, Score-Range).
Route: /explore/browse
View: resources/views/livewire/explore/browse.blade.php
Location: app/Livewire/Explore/Browse.php
Zusaetzliche Properties
| Property | Typ | Default | URL-Param | Beschreibung |
|---|---|---|---|---|
$tag | string | '' | ja | AI-generiertes Keyword filtern |
$minScore | int | 0 | ja | Minimum Postbox Score |
$maxScore | int | 100 | ja | Maximum Postbox Score |
$scoreRange | array | [0, 100] | nein | Flux UI Range Slider State |
Computed Properties
| Property | Cache | Beschreibung |
|---|---|---|
availableTags | 24h | Alle Tags mit >= 2 Verwendungen, ohne Stop-Words und Blocked Tags |
Actions
| Method | Beschreibung |
|---|---|
resetFilters() | Alle Filter inkl. Tag und Score-Range zuruecksetzen |
clearTag() | Tag-Filter loeschen |
addToWorkspace(int $socialProfileId) | Profil in Workspace aufnehmen |
Explore/TrendingVideos
Trending YouTube Videos mit Velocity- und Trending-Score.
Route: /explore/trending-videos
View: resources/views/livewire/explore/trending-videos.blade.php
Location: app/Livewire/Explore/TrendingVideos.php
Nutzt ExploreTrendingVideo-Model mit Recency-Filter. Sortierung nach trending_score * velocity_score.
Lazy Loading (Explore)
Folgende Explore-Components nutzen #[Lazy] fuer asynchrones Laden per XHR (Viewport-basiert):
| Component | Placeholder | Beschreibung |
|---|---|---|
ProfileGrid | Shared Grid: livewire.partials.grid-placeholder (14 Items) | Profil-Card-Skeletons mit Bild, Info, Score-Bar, Button |
TrendingSection | Shared Grid: livewire.partials.grid-placeholder (7 Items + Heading) | Wie ProfileGrid, mit Ueberschrift-Skeleton |
TagCloud | livewire.partials.tag-cloud-placeholder | 20 Pill-Skeletons mit zufaelliger Breite |
CategoryCloud | livewire.partials.category-cloud-placeholder | 12 Badge-Skeletons mit zufaelliger Breite |
Alle Placeholder nutzen <flux:skeleton.group animate="shimmer"> fuer einheitliche Shimmer-Animation. Die Grid-Placeholder sind parametrisierbar via $count (Anzahl Karten) und $heading (Ueberschrift-Skeleton).
Shared Placeholder Views: resources/views/livewire/partials/
Explore/ProfileGrid
Wiederverwendbare Grid-Komponente fuer Profil-Karten.
View: resources/views/livewire/explore/profile-grid.blade.php
Location: app/Livewire/Explore/ProfileGrid.php
Empfaengt Profil-Collection vom Eltern-Component und rendert ExploreProfileCards. Dispatcht addToWorkspace-Events an den Eltern.
Explore/TagCloud
Tag-Wolke mit gewichteter Darstellung basierend auf AI-generierten Keywords.
View: resources/views/livewire/explore/tag-cloud.blade.php
Location: app/Livewire/Explore/TagCloud.php
Daten aus social_profiles.ai_keywords (JSONB), gecacht fuer 24h. Tags werden nach Haeufigkeit gewichtet (Schriftgroesse). Blocked Tags (BlockedTag) und Stop-Words (config('postbox.tag_stop_words')) werden gefiltert.
VideoTrends\Index
Top-Level-Seite fuer Video-spezifische Leaderboards und Trends. Zeigt Score-Rankings, View-Growth-Rankings und aktuell trendende Videos.
Route: /video-trends
View: resources/views/livewire/video-trends/index.blade.php
Location: app/Livewire/VideoTrends/Index.php
Public Properties
| Property | Typ | Default | Beschreibung |
|---|---|---|---|
$duration | string | 'all' | Duration-Filter: all/short/medium/long/extended/extra_long |
$category | string | 'all' | Kategorie-Filter (ExploreCategory slug) |
$country | string | 'all' | Laender-Filter (ISO 3166-1 alpha-2, URL-synced) |
$favoritesFilter | string | 'all' | Favoriten-Filter: all/favorites |
$period | string | '7d' | Zeitraum: 7d/30d (fuer Growth-Berechnung) |
$hasFavorites | bool | false | Ob der User Favoriten hat |
$kpis | array | [] | KPI-Daten (total_scored, avg_score, top_performers, trending_count) |
$topPerformers | array | [] | Top 10 Videos nach hoechstem Score |
$flopPerformers | array | [] | Top 10 Videos nach niedrigstem Score |
$growthWinners | array | [] | Top 10 Videos nach hoechstem View-Growth |
$growthLosers | array | [] | Top 10 Videos nach niedrigstem View-Growth |
$trendingVideos | array | [] | Top 10 Videos nach Trending Score |
Computed Properties
| Property | Cache | Beschreibung |
|---|---|---|
availableCategories | 24h | Aktive Kategorien mit profile_count > 0 |
availableCountries | 24h | Laender mit >= 5 Videos (Join ueber SocialProfile) |
durationLabels | -- | Duration-Filter-Labels (deutsch) |
Sektionen
KPI-Boxen (4 Cards)
| Box | Wert | Quelle |
|---|---|---|
| Videos mit Score | Anzahl Videos mit aktivem Score | YouTubeVideoScore (latest date, score NOT NULL) |
| Ø Video Score | Durchschnittlicher Score | YouTubeVideoScore (latest date) |
| Top Performer | Videos mit Score >= 80 | YouTubeVideoScore (latest date) |
| Im Trend | Trending-Videos | ExploreTrendingVideo::count() |
Alle 4 KPI-Boxen nutzen flux:tooltip mit position="bottom" und erklaerenden Texten (z.B. "Videos mit einem Performance Score von 80 oder hoeher").
Top / Flop Performer (2 Spalten)
Score-basierte Leaderboards: Top 10 nach hoechstem Score (links) und Top 10 nach niedrigstem Score (rechts). Pro Video: Thumbnail, Titel, Kanal, Score-Badge, Duration-Badge.
Datenquelle: YouTubeVideoScore (latest date) + YouTubeVideo + SocialProfile
Groesstes Wachstum / Groesster Rueckgang (2 Spalten)
View-Growth-basierte Leaderboards. Wachstumsfeld abhaengig vom Period-Filter: view_growth_7d (bei 7d) oder view_growth_24h (bei 30d).
Datenquelle: ExploreTrendingVideo (pre-calculated)
Aktuell im Trend (2-Spalten-Grid)
Top 10 Videos nach trending_score. Mit Velocity-Badges und Trending-Score-Bars.
Datenquelle: ExploreTrendingVideo (Scope trending())
Filter-Implementierung
| Filter | Implementierung |
|---|---|
| Duration | Subquery auf YouTubeVideo.duration_seconds (bei Score-Queries) oder direkte WHERE-Klausel (bei Trending-Queries) |
| Kategorie | Subquery auf ExploreProfileMetric.primary_category via social_profile_id |
| Land | Join ueber SocialProfile (country + detected_country via COALESCE). Min. 5 Videos/Land fuer Dropdown. 24h-Cache. |
| Favoriten | whereIn('social_profile_id', $favoriteProfileIds). User-spezifisch, nicht gecacht. |
| Zeitraum | Bestimmt welches Growth-Feld verwendet wird (view_growth_7d vs. view_growth_24h) |
Duration-Filter-Schwellenwerte
| Key | Bereich (Sekunden) | Label |
|---|---|---|
short | 0 - 60 | Kurz (< 1 Min) |
medium | 0 - 180 | Mittel (< 3 Min) |
long | 0 - 900 | Lang (< 15 Min) |
extended | 0 - 1800 | Erweitert (< 30 Min) |
extra_long | 1800+ | Sehr lang (>= 30 Min) |
Caching
Cache::remember("video-trends:kpi:{$duration}:{$category}", 3600, fn() => ...)
Globaler 1h-Cache fuer KPIs und Leaderboards (ohne Favorites-Filter). Favorites-Filter wird in-memory angewendet (user-spezifisch, nicht cachebar).
Sidebar
Neuer Eintrag "Video Trends" mit Icon play-circle zwischen "Tops & Flops" und "Explore".
Cache-Strategie
Alle Explore-Daten basieren auf ExploreProfileMetric, das taeglich um 03:00 UTC durch explore:calculate --type=all neu berechnet wird. Computed Properties mit persist: true und Cache::remember() (24h TTL) verhindern redundante Queries.
Suche & Discover (Discover/Index)
Globale Profil-Suche mit volltext-Matching (PostgreSQL ILIKE), Filter, Bulk-Select, und Workspace-Modal. UI-Label: „Suche & Discover" (DE) / „Search & Discover" (EN).
Route: /discover
View: resources/views/livewire/discover/index.blade.php
Location: app/Livewire/Discover/Index.php
Public Properties
| Property | Typ | Default | URL-Param | Beschreibung |
|---|---|---|---|---|
$search | string | '' | ja | Suchbegriff (min. 2 Zeichen) |
$platform | string | 'all' | ja | Plattform-Filter (all/youtube/instagram) |
$country | string | 'all' | ja | Land-Filter (ISO 3166-1 alpha-2) |
$category | string | 'all' | ja | Kategorie-Filter |
$tier | string | 'all' | ja | Follower-Tier: micro/small/medium/large/mega |
$limit | int | 24 | nein | Maximale Resultat-Anzahl |
$bulkMode | bool | false | nein | Multi-Select aktiv |
$bulkSelectedIds | array | [] | nein | IDs ausgewaehlter Profile |
$selectedProfileId | int|null | null | nein | Profil fuer Workspace-Modal |
$selectedWorkspaceId | int|null | null | nein | Workspace aus Modal-Auswahl |
$showWorkspaceModal | bool | false | nein | Modal sichtbar |
$rateLimited | bool | false | nein | Rate Limiter aktiv |
Computed Properties
| Property | Cache | Beschreibung |
|---|---|---|
results | nein | Suchresultate (ProfileSearchService) |
addedProfileIds | nein | Map profileId => watcherId (bereits getracked) |
recentSearches | nein | Letzte 10 Suchanfragen des Users |
availableCountries | 24h | Laender mit >= 10 Profilen |
availableCategories | 24h | Aktive Kategorien mit Profile-Count |
userWorkspaces | nein | Regulaere Workspaces (keine Favorites/Admin) |
Actions
| Method | Beschreibung |
|---|---|
addToWorkspace(int $profileId) | Single-Add mit Modal (falls >1 Workspace) |
confirmAddToWorkspace() | Modal bestaetigt, fuehrt Add aus |
bulkAddToWorkspace() | Alle selected Profile adden |
confirmBulkAddToWorkspace() | Bulk Modal-Bestaetigung |
toggleBulkSelect(int $profileId) | Profil togglen in Auswahl |
toggleBulkMode() | Bulk-Mode an/aus, cleared Selection |
applySearch(string $query) | Recent Search uebernehmen |
loadMore() | Limit um 24 erhoehen (pagination) |
resetFilters() | Alle Filter zurueck auf defaults |
Suchverhalten
Rate Limiting: 5 neue Suchanfragen pro 10 Sekunden pro User.
- Wird nur fuer neue Queries gezaehlt, nicht fuer Filter-Aenderungen
updatedSearch()prueft MinLength, rateLimiter Key istdiscover-search:{userId}- Bei Limit: Toast-Warning,
$rateLimited = true, Results = empty
Suchhistorie:
- Nur nicht-leere Ergebnisse werden in
SearchHistoryaufgezeichnet SearchHistory::record($userId, $query)mit DeduplizierungrecentSearches()liefert letzte 10 unique Queries per User
Filter & Multisearch
Alle Filter sind URL-Parameter:
/discover?search=tech&platform=youtube&country=DE&tier=large&category=technology
Filter-Aenderungen triggern:
limit = 24(paginierung reset)bulkSelectedIds = [](auswahl clear)
Workspace-Modal
Zeigt sich, wenn:
- User hat >1 Workspace → Modal fordert Workspace-Auswahl
- Single-Add oder Bulk-Add zu Multi-Workspace-User
- Unterschiedliches Template fuer Single vs. Bulk (selectedProfileId null bei Bulk)
Flow:
- User klickt "+ Workspace" auf Profil-Karte
addToWorkspace($id)checkt Workspace-Count- Falls >1:
showWorkspaceModal = true, Modal oeffnet - User waehlt Workspace, klickt bestaetigen
confirmAddToWorkspace()oderconfirmBulkAddToWorkspace()performAdd()/performBulkAdd()erstellen Watcher + WatcherSource- Toast, Modal schliesst,
addedProfileIdsrecomputed
Transaktionen & Duplikat-Check
Beide performAdd() und performBulkAdd() nutzen:
DB::transaction()fuer Atomitaet (Watcher + Source zusammen)WatcherSource::query()->whereHas('watcher', ...)Check ob bereits existiert- Skip bei existierendem Watcher im Workspace
Keyboad Navigation
Frontend-Feature (view): Arrow Keys + Enter in Search-Input, Escape zum Schliessen.
Location: resources/views/livewire/discover/index.blade.php
Matomo Tracking
Nutzt TracksMatomo Concern:
$this->trackEvent('discover', 'add_to_workspace', $profile->handle ?? '', $profileId);
ProfileSearchService
Service fuer Volltextsuche ueber alle Profiles.
Location: app/Services/Search/ProfileSearchService.php
Public Interface
public function search(string $query, array $filters = [], int $limit = 24): Builder
Parameter:
$query– Suchstring (wird normalisiert)$filters– Optionale Filter:['platform' => '...', 'country' => '...', 'tier' => '...', 'category' => '...']$limit– Max. Resultate (default 24)
Return: Eloquent Builder (nicht executed, kann weiter gefiltert werden).
Implementierung
Nutzt PostgreSQL ILIKE fuer Case-Insensitive Matching:
- Sucht in:
handle,title,description - Filter werden per Scope angewendet
- Ausschluss:
blocked_at,sanitized_at,archived_at(immer) - Min. Follower-Threshold fuer Discovery
Min Query Length
Konstante ProfileSearchService::MIN_QUERY_LENGTH (default: 2 Zeichen).