diff --git a/resources/views/livewire/profile/experiments-manager.blade.php b/resources/views/livewire/profile/experiments-manager.blade.php index a407ef0..93ed2bb 100644 --- a/resources/views/livewire/profile/experiments-manager.blade.php +++ b/resources/views/livewire/profile/experiments-manager.blade.php @@ -1,6 +1,5 @@ currentView = $this->hasExperiments ? 'list' : 'no-content'; } - /** - * Determine if there are any experiments available. - */ #[Computed] public function hasExperiments(): bool { - $hasExperiments = $this->experiments->isNotEmpty(); - Log::debug('Checking if has experiments', ['hasExperiments' => $hasExperiments]); - return $hasExperiments; + return $this->experiments->isNotEmpty(); } - /** - * Retrieve and process all available experiments. - */ #[Computed] public function experiments(): Collection { - $allFeatures = Feature::all(); - - $experiments = collect($allFeatures) - ->mapWithKeys(function ($value, $key) { - return [$key => $this->getExperimentDetails($key)]; - }); - - Log::debug('Processed experiments', ['count' => $experiments->count(), 'experiments' => $experiments]); - return $experiments; + return collect(Feature::all())->mapWithKeys(fn ($value, $key) => [$key => $this->getExperimentDetails($key)]); } /** - * Toggle the active state of an experiment for the current user. + * Toggles the state of an experiment for the current user. + * + * @param string $experiment The name of the experiment to toggle */ public function toggleExperiment(string $experiment): void { - $user = $this->getCurrentUser(); - $isActive = Feature::for($user)->active($experiment); + $user = Auth::user(); + if (!$user) { + return; + } - Log::debug('Toggling experiment', ['experiment' => $experiment, 'currentState' => $isActive]); + $isActive = Feature::for($user)->active($experiment); + $experimentTitle = $this->getExperimentTitle($experiment); if ($isActive) { - Toaster::success("Experiment '{$this->getExperimentTitle($experiment)}' has been disabled."); Feature::for($user)->deactivate($experiment); - } else { - Toaster::success("Experiment '{$this->getExperimentTitle($experiment)}' has been successfully enabled."); - Feature::for($user)->activate($experiment); + Toaster::success("Experiment '{$experimentTitle}' has been disabled."); + $this->dispatch('experiment-toggled'); + return; } + Feature::for($user)->activate($experiment); + Toaster::success("Experiment '{$experimentTitle}' has been enabled."); $this->dispatch('experiment-toggled'); } - /** - * Switch to the experiments list view. - */ public function viewExperiments(): void { $this->currentView = 'list'; } /** - * Open the details modal for a specific experiment. + * Displays detailed information about a specific experiment. + * + * @param string $experiment The name of the experiment to view */ public function viewExperimentDetails(string $experiment): void { $this->selectedExperiment = $experiment; - Log::debug('Viewing experiment details', ['experiment' => $experiment]); $this->dispatch('open-modal', 'experiment-details'); } - /** - * Open the feedback modal and close the experiment details modal. - */ public function openFeedbackModal(): void { $this->showFeedbackModal = true; @@ -127,46 +99,103 @@ public function openFeedbackModal(): void } /** - * Submit user feedback for the selected experiment. + * Submits user feedback to the external feedback service. + * + * Validates input, sends the feedback, and handles the response. */ public function submitFeedback(): void + { + $this->validateFeedback(); + + try { + $response = $this->sendFeedbackRequest(); + $this->handleFeedbackResponse($response); + } catch (Exception $e) { + $this->handleFeedbackException($e); + } + } + + /** + * Validates the feedback form data. + * + */ + private function validateFeedback(): void { $this->validate([ 'selectedExperiment' => 'required|string|max:255', 'feedbackText' => 'required|string|max:10000', 'feedbackEmail' => 'nullable|email|max:255', ]); + } + /** + * Sends the feedback request to the external service. + * + * @return \Illuminate\Http\Client\Response + */ + private function sendFeedbackRequest(): \Illuminate\Http\Client\Response + { $feedbackServiceUrl = 'https://feedback.vanguardbackup.com/api/feedback'; - try { - $response = Http::post($feedbackServiceUrl, [ - 'experiment' => trim($this->selectedExperiment), - 'feedback' => trim($this->feedbackText), - 'php_version' => trim(phpversion()), - 'vanguard_version' => trim(obtain_vanguard_version()), - 'email_address' => $this->feedbackEmail ? trim($this->feedbackEmail) : null, - ]); - - if ($response->successful() && $response->json('status') === 'success') { - Toaster::success($response->json('message', 'Thank you for your feedback!')); - $this->resetFeedbackForm(); - } elseif ($response->status() === 422) { - $this->handleValidationErrors($response->json('errors')); - } elseif ($response->status() === 429) { - Toaster::error('Too many requests. Please try again later.'); - } else { - throw new RuntimeException('Unexpected response from feedback service'); - } - } catch (Exception $e) { - Log::error('Failed to submit feedback', ['error' => $e->getMessage()]); - Toaster::error('Failed to submit feedback. Please try again later.'); + return Http::post($feedbackServiceUrl, [ + 'experiment' => trim($this->selectedExperiment), + 'feedback' => trim($this->feedbackText), + 'php_version' => trim(phpversion()), + 'vanguard_version' => trim(obtain_vanguard_version()), + 'email_address' => $this->feedbackEmail ? trim($this->feedbackEmail) : null, + ]); + } + + /** + * Handles the response from the feedback service. + * + * @param \Illuminate\Http\Client\Response $response + * @throws RuntimeException + */ + private function handleFeedbackResponse(\Illuminate\Http\Client\Response $response): void + { + if (!$response->successful()) { + $this->handleUnsuccessfulResponse($response); + } + + if ($response->json('status') !== 'success') { + throw new RuntimeException('Unexpected response status from feedback service'); + } + + Toaster::success($response->json('message', 'Your feedback has been successfully submitted. Thank you!')); + $this->resetFeedbackForm(); + } + + /** + * Handles unsuccessful responses from the feedback service. + * + * @param \Illuminate\Http\Client\Response $response + * @throws \RuntimeException + */ + private function handleUnsuccessfulResponse(\Illuminate\Http\Client\Response $response): void + { + if ($response->status() === 422) { + $this->handleValidationErrors($response->json('errors')); + } + + if ($response->status() === 429) { + Toaster::error('Too many requests. Please try again later.'); } + + throw new RuntimeException('Unexpected response from feedback service'); } /** - * Reset the feedback form after successful submission. + * Handles exceptions that occur during the feedback submission process. + * + * @param Exception $e */ + private function handleFeedbackException(\Exception $e): void + { + Log::error('Failed to submit feedback', ['error' => $e->getMessage()]); + Toaster::error('We encountered an issue while submitting your feedback. Please try again later.'); + } + private function resetFeedbackForm(): void { $this->feedbackText = ''; @@ -177,7 +206,9 @@ private function resetFeedbackForm(): void } /** - * Handle validation errors from the API response. + * Handles validation errors from the feedback service. + * + * @param array $errors */ private function handleValidationErrors(array $errors): void { @@ -188,9 +219,6 @@ private function handleValidationErrors(array $errors): void throw ValidationException::withMessages($messages); } - /** - * Close the feedback modal and reopen the experiment details modal. - */ public function closeFeedbackModal(): void { $this->showFeedbackModal = false; @@ -199,28 +227,27 @@ public function closeFeedbackModal(): void } /** - * Retrieve detailed information about a specific experiment. + * Retrieves detailed information about a specific experiment. + * + * @param string $experiment The name of the experiment + * @return array */ private function getExperimentDetails(string $experiment): array { - $user = $this->getCurrentUser(); + $user = Auth::user(); + if (!$user) { + return []; + } + $isActive = Feature::active($experiment) ?? false; $isEnabled = Feature::for($user)->active($experiment) ?? false; $metadata = config("features.{$experiment}", []); - Log::debug('Getting experiment details', [ - 'experiment' => $experiment, - 'isActive' => $isActive, - 'isEnabled' => $isEnabled, - 'metadata' => $metadata, - ]); - return [ 'name' => $experiment, 'title' => $metadata['title'] ?? $this->getExperimentTitle($experiment), 'description' => $metadata['description'] ?? $this->getExperimentDescription($experiment), 'group' => $metadata['group'] ?? 'Uncategorized', - 'rolloutPercentage' => $metadata['rolloutPercentage'] ?? 0, 'icon' => $metadata['icon'] ?? 'heroicon-o-beaker', 'active' => $isActive, 'enabled' => $isEnabled, @@ -228,7 +255,10 @@ private function getExperimentDetails(string $experiment): array } /** - * Generate a human-readable title for an experiment. + * Generates a title for an experiment based on its name. + * + * @param string $experiment The name of the experiment + * @return string */ private function getExperimentTitle(string $experiment): string { @@ -236,23 +266,17 @@ private function getExperimentTitle(string $experiment): string } /** - * Generate a default description for an experiment. + * Generates a description for an experiment based on its name. + * + * @param string $experiment The name of the experiment + * @return string */ private function getExperimentDescription(string $experiment): string { return "This is the {$this->getExperimentTitle($experiment)} experiment."; } - - /** - * Retrieve the current authenticated user. - */ - private function getCurrentUser(): User - { - $user = auth()->user(); - Log::debug('Current user retrieved', ['userId' => $user->id]); - return $user; - } -}; ?> +}; +?>
@if ($currentView === 'no-content') @@ -261,43 +285,40 @@ private function getCurrentUser(): User @svg('heroicon-o-beaker', 'h-16 w-16 text-primary-900 dark:text-white inline') - {{ __('No Experiments') }} + {{ __('No Active Experiments') }} - {{ __('Sorry, there are no experiments available for your account at the moment.') }} + {{ __('There are currently no experiments available for your account.') }} @elseif ($currentView === 'list') - {{ __('Experiments') }} + {{ __('Feature Experiments') }} - {{ __('Explore and manage experiments for your account.') }} + {{ __('Explore and manage experimental features for your account.') }} heroicon-o-beaker
@svg('heroicon-o-light-bulb', 'w-8 h-8 text-blue-500 mr-3') -

{{ __('What are Experiments?') }}

+

{{ __('About Feature Experiments') }}

- {{ __('Experiments are controlled rollouts of new functionalities or improvements to our application. They allow us to test new ideas, gather feedback, and ensure stability before full release. By participating in these experiments, you help shape the future of our product.') }} + {{ __('Feature experiments allow us to introduce new functionalities and improvements in a controlled manner. By participating, you help shape the future of our product and enjoy early access to new features.') }}

  • @svg('heroicon-o-check-circle', 'w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0') - {{ __('Test new features before they\'re widely available') }} + {{ __('Access new features before wide release') }}
  • @svg('heroicon-o-check-circle', 'w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0') - {{ __('Provide valuable feedback to improve features') }} + {{ __('Provide valuable insights to enhance functionality') }}
  • @svg('heroicon-o-check-circle', 'w-5 h-5 text-blue-500 mr-2 mt-0.5 flex-shrink-0') - {{ __('Customize your experience by enabling or disabling specific experiments') }} + {{ __('Tailor your experience by managing experiment participation') }}
@@ -307,8 +328,7 @@ class="text-blue-700 dark:text-blue-300">{{ __('Customize your experience by ena

{{ $group }}

@foreach ($groupExperiments as $experiment) -
+
@@ -318,7 +338,7 @@ class="border border-gray-200 dark:border-gray-600 rounded-lg transition-all dur

{{ $experiment['title'] }}

- {{ $experiment['enabled'] ? __('Enabled for you') : __('Disabled for you') }} + {{ $experiment['enabled'] ? __('Active for your account') : __('Inactive for your account') }}

@@ -327,7 +347,7 @@ class="border border-gray-200 dark:border-gray-600 rounded-lg transition-all dur wire:click="viewExperimentDetails('{{ $experiment['name'] }}')" class="mr-3" > - {{ __('View Details') }} + {{ __('More Info') }}
@@ -341,10 +361,10 @@ class="mr-3" - {{ __('Experiment Details') }} + {{ __('Experiment Information') }} - {{ __('Learn more about this experiment and manage its status.') }} + {{ __('Learn more about this experiment and manage your participation.') }} heroicon-o-information-circle @@ -363,25 +383,21 @@ class="mr-3" {{ $experiment['description'] }}

- {{ __('Status:') }} - - {{ $experiment['enabled'] ? __('Enabled') : __('Disabled') }} - + {{ __('Status:') }} + + {{ $experiment['enabled'] ? __('Active') : __('Inactive') }} +
- {{ __('Group:') }} + {{ __('Category:') }} {{ $experiment['group'] }}
-
+

{{ __('Note:') }} - {{ __('You may need to reload the page (F5 or Cmd/Ctrl + R) to see the effects of enabling or disabling an experiment.') }} + {{ __('You may need to refresh the page (F5 or Cmd/Ctrl + R) to see changes after activating or deactivating an experiment.') }}

@@ -391,14 +407,14 @@ class="mb-4 p-3 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-0" > @svg('heroicon-o-chat-bubble-left-right', 'w-5 h-5 mr-2 -ml-1') - {{ __('Give Feedback') }} + {{ __('Share Feedback') }}
{{ __('Close') }} - {{ $experiment['enabled'] ? __('Disable Experiment') : __('Enable Experiment') }} + {{ $experiment['enabled'] ? __('Deactivate') : __('Activate') }}
@@ -407,36 +423,48 @@ class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-w - {{ __('Provide Feedback') }} + {{ __('Share Your Thoughts') }} - {{ __('Share your thoughts on this experiment to help us improve.') }} + {{ __('Your feedback helps us improve this experimental feature.') }} heroicon-o-chat-bubble-left-right
-
- - - - -

- {{ __('Note: Your Vanguard version and PHP version will be shared with this feedback. No other information will be included.') }} +

+
+ + + +
+ +
+ + + + {{ __('Provide your email if you\'d like us to follow up on your feedback.') }} + + +
+ +

+ {{ __('Note: Your Vanguard version and PHP version will be included with your feedback to help us address any potential technical issues.') }}

@@ -444,11 +472,11 @@ class="mt-3 block w-full" {{ __('Cancel') }} - + {{ __('Submit Feedback') }}
- @endif
+@endif