Zum Hauptinhalt springen

Shared Components

Layout- und Feature-Components, die uebergreifend in der gesamten Applikation eingebunden werden.

WithWorkspaceSelection (Trait)

Shared Trait fuer alle "Hinzufuegen"-Buttons in der App. Oeffnet IMMER einen Workspace-Auswahl-Dialog — auch bei nur einem Workspace. Inklusive Inline-Workspace-Erstellung und Session-basiertem "letzter Workspace"-Memory.

Location: app/Concerns/WithWorkspaceSelection.php Modal: resources/views/components/workspace-selection-modal.blade.php Tests: tests/Feature/Livewire/WorkspaceSelectionTest.php (15 Tests)

Public Properties

PropertyTypDefaultBeschreibung
$showWorkspaceModalboolfalseModal sichtbar
$selectedWorkspaceIdint|nullnullGewaehlter Workspace (Radio)
$pendingProfileIdint|nullnullProfil-ID fuer Single-Add
$pendingBulkIdsarray[]Profil-IDs fuer Bulk-Add
$showCreateWorkspaceboolfalseInline-Erstellungsformular
$newWorkspaceNamestring''Name fuer neuen Workspace

Computed Properties

PropertyTypBeschreibung
selectableWorkspacesCollection<Workspace>Workspaces ohne Favorites + Admin, mit withCount('watchers')

Actions

MethodBeschreibung
openWorkspaceSelection(int $profileId)Modal fuer Single-Add oeffnen
openBulkWorkspaceSelection(array $ids)Modal fuer Bulk-Add oeffnen
confirmWorkspaceSelection()Auswahl bestaetigen → performAddToWorkspace() aufrufen
createAndSelectWorkspace()Workspace inline erstellen und auswaehlen
resetWorkspaceSelection()Modal-State zuruecksetzen
performAddToWorkspace(int $profileId, int $workspaceId)Default-Implementierung: Watcher + WatcherSource erstellen
performBulkAddToWorkspace(array $ids, int $workspaceId)Default: Ruft performAddToWorkspace() pro Profil auf

Datenfluss

graph TD
A["Button: openWorkspaceSelection(profileId)"] --> B["Modal oeffnen"]
B --> C{"User waehlt Workspace"}
C -->|"Bestehender Workspace"| D["confirmWorkspaceSelection()"]
C -->|"Neuer Workspace"| E["createAndSelectWorkspace()"]
E --> D
D --> F{"Single oder Bulk?"}
F -->|"Single"| G["performAddToWorkspace()"]
F -->|"Bulk"| H["performBulkAddToWorkspace()"]
G --> I["Watcher + WatcherSource erstellen"]
H --> G
I --> J["Toast + Modal schliessen"]

Verwendende Components (12)

ComponentOverrideZusatz-Logik
Explore\BrowseperformAddToWorkspaceMatomo Tracking (Alias-Pattern)
Explore\IndexDefault-Implementierung
Explore\ProfileGridDefault-Implementierung
Explore\TrendingVideosDefault-Implementierung
Discover\IndexperformAddToWorkspace, performBulkAddToWorkspaceMatomo + UI-Refresh
TopsFlops\IndexperformAddToWorkspaceMatomo + loadData() Refresh
VideoTrends\IndexDefault-Implementierung
Dashboard\RecommendedProfilesDefault-Implementierung
Dashboard\TrendingSliderDefault-Implementierung
Watchers\RelatedChannelsDefault-Implementierung
Watchers\RelatedProfilesDefault-Implementierung
Watchers\CrossPlatformRelatedDefault-Implementierung

Override-Pattern

Components mit Custom-Logik nutzen PHP Trait-Aliasing:

use WithWorkspaceSelection {
WithWorkspaceSelection::performAddToWorkspace as traitPerformAddToWorkspace;
}

public function performAddToWorkspace(int $profileId, int $workspaceId): void
{
$this->traitPerformAddToWorkspace($profileId, $workspaceId);
$this->trackEvent('explore', 'add_to_workspace', '', $profileId);
}

Blade-Integration

WICHTIG: @include() statt <x-...> verwenden — anonyme Blade Components haben isolierten Scope.

{{-- Am Ende des Component-Views: --}}
@include('components.workspace-selection-modal')

WorkspaceSwitcher

Workspace-Dropdown im Header fuer den Kontext-Wechsel.

View: resources/views/livewire/workspace-switcher.blade.php Location: app/Livewire/WorkspaceSwitcher.php

Public Properties

PropertyTypBeschreibung
$currentWorkspaceIdint|nullAktiver Workspace
$workspacesCollectionAlle User-Workspaces

Actions

MethodBeschreibung
switchWorkspace(int $id)Workspace wechseln, Session aktualisieren

Events

RichtungEventBeschreibung
dispatchworkspace-switchedNach Workspace-Wechsel (andere Components reagieren)
listenworkspaces-updatedWorkspace-Liste neu laden (nach Rename/Create/Delete)

Der Switcher filtert Favorites-Workspace und Admin-Workspace aus der Anzeige fuer Non-Admin-User. Admin-User sehen den Admin-Workspace als separaten Eintrag.

Sidebar-Sortierung: Favoriten zuerst, dann Admin-View, danach alphabetisch. Auf der Workspace-Verwaltungsseite (/workspaces) ist die Sortierung invertiert: System-Workspaces stehen ganz unten.

Das workspaces-updated Event wird von Workspaces\Index nach Create, Rename und Delete dispatcht — der Switcher laedt die Liste daraufhin sofort neu.


NotificationCenter

Globaler WebSocket Event Handler. In das Haupt-Layout eingebettet, empfaengt und verarbeitet alle Reverb-Broadcasts.

View: resources/views/livewire/notification-center.blade.php Location: app/Livewire/NotificationCenter.php

Public Properties

PropertyTypBeschreibung
$userIdintAktueller User (0 fuer Guests)
$isAdminboolAdmin-Status
$workspaceIdsarrayIDs aller User-Workspaces
$subscribedProfileIdsarrayDynamisch abonnierte Profil-Channels

Event Handlers (#[On])

Event (Listen)HandlerPersistenz
reverb-import-progressonImportProgress()Toast + Nachrichtenzentrale bei Finish
reverb-youtube-sync-completedonYouTubeSyncCompleted()Toast + Nachrichtenzentrale (nur manuell)
reverb-daily-scrape-completedonDailyScrapeCompleted()Toast + Nachrichtenzentrale
reverb-user-registeredonUserRegistered()Toast + Nachrichtenzentrale (Admin only)
reverb-related-profiles-calculatedonRelatedProfilesCalculated()Toast + Nachrichtenzentrale (nur manuell)
reverb-profile-reactivatedonProfileReactivated()Toast + Nachrichtenzentrale
reverb-profile-sanitizedonProfileSanitized()Toast + Nachrichtenzentrale
reverb-profile-unsanitizedonProfileUnsanitized()Toast + Nachrichtenzentrale
reverb-server-alert-triggeredonServerAlertTriggered()Nur Toast
subscribe-profilesubscribeToProfile(int $id)--

Dispatch Events

Jeder Handler dispatcht:

  1. show-toast -- Sofortige Benachrichtigung
  2. Spezifisches Event (z.B. import-progress-updated, youtube-sync-completed) -- Fuer lauschende Child-Components
  3. notification-received -- Bell-Badge aktualisieren

JS-Integration

Channel-Subscriptions werden via JavaScript (subscribeToAllChannels() in Alpine) gehandhabt -- nicht ueber Livewires Echo-Integration. Das stellt sicher, dass die WebSocket-Verbindung zuerst aufgebaut wird, bevor Channels abonniert werden.

Rate-Limiting: safeServerDispatch() begrenzt Server-Calls auf max 40 pro Burst.


NotificationBell

Glocken-Icon mit Unread-Badge im Header.

View: resources/views/livewire/notification-bell.blade.php Location: app/Livewire/NotificationBell.php

Public Properties

PropertyTypBeschreibung
$unreadCountintUngelesene Benachrichtigungen
$loadedboolOb Dropdown schon geoeffnet wurde (steuert DB-Query)
$refreshKeyintWird bei jedem Open inkrementiert fuer DOM-Replacement

Events

RichtungEventBeschreibung
listennotification-receivedUnread-Count neu laden, $loaded zuruecksetzen

Wichtig: @persist-Verhalten

Die Komponente ist mit @persist('notification-bell-sidebar') eingebunden und ueberlebt wire:navigate. Daher:

  • Kein DB-Query bei initialem Render$loaded=false verhindert stalen DOM im @persist-Cache
  • Alpine Loading-State — Spinner statt Stale-Content beim Oeffnen ($wire.openDropdown().then())
  • refreshUnreadCount() setzt $loaded=false — WebSocket-Events invalidieren alten Content

Notifications/Index

Vollseiten-Ansicht aller Benachrichtigungen mit Mark-as-Read und Dismiss.

Route: /notifications View: resources/views/livewire/notifications/index.blade.php Location: app/Livewire/Notifications/Index.php

Actions

MethodBeschreibung
markAsRead(int $id)Einzelne Benachrichtigung als gelesen markieren
markAllAsRead()Alle als gelesen markieren
dismiss(int $id)Benachrichtigung ausblenden (dismissed_at)
dismissAll()Alle ausblenden
loadMore()Weitere Eintraege laden (Infinite Scroll)

Feedback/ReportIssue

Unified Feedback-Widget mit Gast-Support, Profil-Kontext, Screenshot-Capture und Contact-Modus. Ersetzt auch das alte ContactForm-Component.

View: resources/views/livewire/feedback/report-issue.blade.php Location: app/Livewire/Feedback/ReportIssue.php

Modi

ModusBeschreibungVerwendung
feedback (Default)Bug-Report / Feature-Request ModalWatcher-Seiten, Explorer-Seiten, Sidebar
contactKontaktformular auf CMS-Seiten/kontakt-Seite (ersetzt das alte ContactForm)

Gast-Support

Nicht-angemeldete Benutzer koennen Feedback geben. Zusaetzliche Felder fuer Gaeste:

  • Name (Pflicht)
  • E-Mail (Pflicht)
  • Privacy-Checkbox (Pflicht)
  • Honeypot-Feld (Spam-Schutz)
  • Cloudflare Turnstile (Bot-Schutz, nur wenn aktiviert)

Gast-Einreichungen werden in contact_submissions mit source='feedback' archiviert.

Profil-Kontext

Auf Explorer-Profilseiten (/explorer/{slug}_{id}) werden Profil-Daten automatisch mitgeschickt:

PropertyBeschreibung
$profileIdSocial Profile ID
$profileNameProfilname
$profilePlatformPlattform (youtube/instagram)
$profileUrlExplorer-Profil-URL

Screenshot-Capture

One-Click Screenshot via html2canvas-pro (Vite lazy-loaded, ~200 kB Chunk). Erfasst den aktuellen Seiteninhalt als PNG und haengt ihn automatisch als Attachment an das Feedback an.

Der Screenshot-Button nutzt Alpine.js-State (captured, failed, capturing) fuer sofortiges visuelles Feedback:

  • Capturing: "Wird aufgenommen..." mit deaktiviertem Button
  • Captured: Gruener Text "Screenshot angehaengt" ersetzt den Button
  • Failed: Roter Text "Screenshot fehlgeschlagen" wird angezeigt

Waehrend der Aufnahme werden dialog[open]-Elemente (Flux UI Modals) temporaer ausgeblendet, damit das Modal nicht im Screenshot erscheint.

Flux UI Custom Element Handling: Flux rendert UI-Komponenten als ui-* HTML-Tags (ui-sidebar, ui-button, ui-chart etc.). Nur ui-chart ist ein registrierter Custom Element (customElements.define), die anderen sind einfache HTML-Tags. html2canvas-pro kann diese im geklonten Dokument nicht korrekt rendern. Der onclone-Callback loest drei Probleme:

  1. Custom Elements → div-Replacement: Alle Tags mit Hyphen werden mit <div>-Wrappern ersetzt. Children (Light DOM: Chart-SVGs, Button-Elemente, Sidebar-Inhalte) bleiben erhalten. Die computed display-Values der Originalelemente werden als Inline-Style uebertragen.
  2. SVG-Logo Inlining: <img src="*.svg">-Tags werden zu Inline-<svg> konvertiert. html2canvas rendert externe SVG-Bilder manchmal als graue Bloecke.
  3. Nur strukturelle Elemente ignoriert: ignoreElements filtert nur DIALOG, TEMPLATE, SCRIPT und NOSCRIPT — kein pauschales Custom-Element-Skipping mehr.

Location: resources/js/screenshot-capture.js

Public Properties

PropertyTypDefaultBeschreibung
$showboolfalseModal sichtbar
$modestring'feedback'Modus: feedback oder contact
$watcherIdint|nullnullKontext: Watcher-ID
$watcherNamestring|nullnullKontext: Watcher-Name
$profileIdint|nullnullKontext: Explorer-Profil-ID
$profileNamestring|nullnullKontext: Explorer-Profilname
$profilePlatformstring|nullnullKontext: Plattform
$profileUrlstring|nullnullKontext: Explorer-Profil-URL
$categorystring'bug'Feedback-Kategorie (aus config('feedback.categories'))
$subjectstring''Betreff (max. 100 Zeichen)
$messagestring''Nachricht (max. 5000 Zeichen)
$attachmentsarray[]Datei-Uploads
$guestNamestring''Gast: Name
$guestEmailstring''Gast: E-Mail
$privacyAcceptedboolfalseGast: Privacy-Checkbox

Kategorien

Konfigurierbar via config('feedback.categories'):

KeyLabel
bugFehler melden
featureFeature-Request
questionFrage
data-issueDatenproblem
block-requestSperranfrage
profile-suggestionProfil vorschlagen
otherSonstiges

Actions

MethodBeschreibung
open()Modal oeffnen (bewahrt via mount() gesetzten Kontext)
openWithWatcher(Watcher $watcher)Modal mit Watcher-Kontext oeffnen
openWithProfile(array $data)Modal mit Explorer-Profil-Kontext oeffnen
close()Modal schliessen, Formular zuruecksetzen
submit()Feedback validieren und per E-Mail senden
removeAttachment(int $index)Datei-Anhang entfernen
captureScreenshot()html2canvas Screenshot als PNG-Attachment

Rate-Limiting

Max. 30 Feedback-Mails pro Stunde pro User/IP (konfigurierbar via FEEDBACK_RATE_LIMIT env). Bei Ueberschreitung wird eine Wartemeldung angezeigt.

Datei-Upload Limits

Validierung via ValidatesAttachments Trait (app/Concerns/ValidatesAttachments.php):

LimitWert
Max. pro Datei5 MB
Max. gesamt15 MB
Erlaubte TypenJPEG, PNG, GIF, WebP, HEIC, MP4, MOV

Dateien werden temporaer unter storage/app/feedback-attachments/ gespeichert und an die queued FeedbackReport Mailable angehaengt.

Explorer-Integration

Feedback-Button auf folgenden oeffentlichen Explorer-Seiten:

  • /explorer (Index)
  • /explorer/category/{slug} (Category Landing)
  • /explorer/tag/{slug} (Tag Landing)
  • /explorer/{slug}_{id} (Profilseite — mit automatischem Profil-Kontext)

Settings/Notifications

Benachrichtigungs-Einstellungen pro Notification-Typ.

Route: /settings/notifications View: resources/views/livewire/settings/notifications.blade.php Location: app/Livewire/Settings/Notifications.php

Erlaubt Usern, pro Notification-Typ (Import, Sync, Scrape, etc.) festzulegen, ob sie Benachrichtigungen per Toast, Nachrichtenzentrale und/oder E-Mail erhalten moechten. Einstellungen werden in der notification_preferences-Tabelle gespeichert.


Settings/TwoFactor

Verwaltung der Zwei-Faktor-Authentifizierung (TOTP) in den User-Einstellungen.

Route: /settings/two-factor View: resources/views/livewire/settings/two-factor.blade.php Location: app/Livewire/Settings/TwoFactor.php

Public Properties

PropertyTypDefaultBeschreibung
$showSetupboolfalseQR-Code + Secret anzeigen
$showRecoveryCodesboolfalseRecovery Codes anzeigen
$confirmationCodestring''Eingegebener TOTP-Code

Computed Properties

PropertyTypBeschreibung
twoFactorEnabledboolSecret vorhanden
twoFactorConfirmedbooltwo_factor_confirmed_at gesetzt
qrCodeSvgstring|nullSVG-QR-Code fuer Authenticator
setupKeystring|nullEntschluesselter Secret-Key
recoveryCodesarray8 Backup-Codes

Actions

MethodBeschreibung
enableTwoFactor()2FA aktivieren (generiert Secret + Codes)
confirmTwoFactor()6-stelligen TOTP-Code validieren
regenerateRecoveryCodes()Neue Recovery Codes generieren
disableTwoFactor()2FA deaktivieren

Nutzt Fortify-Actions (EnableTwoFactorAuthentication, ConfirmTwoFactorAuthentication, GenerateNewRecoveryCodes, DisableTwoFactorAuthentication) direkt statt HTTP-Routes.

Detaillierter Ablauf: siehe Authentifizierung → 2FA.


Overview/Index

Landing-Seite nach Registrierung fuer neue User.

Route: /overview View: resources/views/livewire/overview/index.blade.php Location: app/Livewire/Overview/Index.php

Seitentitel: "Uebersicht". Fortify leitet nach Registration automatisch hierher weiter (konfiguriert in config/fortify.php). Zeigt Onboarding-Informationen fuer neue User.