Skip to content

Commit

Permalink
feat: Added quiet mode
Browse files Browse the repository at this point in the history
  • Loading branch information
lewislarsen committed Aug 21, 2024
1 parent 3bd9d4a commit 59ed941
Show file tree
Hide file tree
Showing 20 changed files with 693 additions and 0 deletions.
100 changes: 100 additions & 0 deletions app/Console/Commands/ResetQuietModeStatus.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Mail\User\QuietModeExpiredMail;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Mail;
use Symfony\Component\Console\Command\Command as CommandAlias;

/**
* Class ResetQuietModeStatus
*
* This command resets the quiet mode for users whose quiet mode has expired.
* It processes users in chunks to efficiently handle large datasets.
*/
class ResetQuietModeStatus extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'vanguard:reset-quiet-mode';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Resets the quiet mode for users whose quiet mode has expired.';

/**
* The count of users whose quiet mode was reset.
*/
protected int $resetCount = 0;

/**
* Execute the console command.
*
* This method initiates the process of resetting expired quiet modes,
* provides informational output, and returns the command's exit status.
*/
public function handle(): int
{
$this->components->info('Starting to reset expired quiet modes...');

$this->resetExpiredQuietModes();

$this->components->info("Quiet mode reset for {$this->resetCount} users.");

return CommandAlias::SUCCESS;
}

/**
* Reset quiet mode for users whose quiet mode has expired.
*
* This method queries the database for users with expired quiet mode
* and processes them in chunks to efficiently handle large datasets.
*/
protected function resetExpiredQuietModes(): void
{
User::query()
->whereDate('quiet_until', '<=', Carbon::today())
->whereNotNull('quiet_until')
->chunkById(100, function ($users): void {
foreach ($users as $user) {
$this->resetUserQuietMode($user);
}
});
}

/**
* Reset quiet mode for a single user and increment the reset count.
*
* This method clears the quiet mode for the given user, increments the reset count,
* and logs an informational message about the action. It handles both User models
* and generic Eloquent models for flexibility.
*
* @param User|Model $user The user whose quiet mode is being reset
*/
protected function resetUserQuietMode(User|Model $user): void
{
if ($user instanceof User) {
$user->clearQuietMode();

Mail::to($user)->queue(new QuietModeExpiredMail($user));

$this->resetCount++;

$this->info("Quiet mode reset for user: {$user->getAttribute('email')}");
} else {
$this->warn('Unexpected model type encountered. Expected User, got ' . $user::class);
}
}
}
32 changes: 32 additions & 0 deletions app/Livewire/Profile/QuietModePage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace App\Livewire\Profile;

use Illuminate\View\View;
use Livewire\Component;

/**
* Class QuietModePage
*
* This Livewire component represents the Quiet Mode page in the user's profile section.
* It is responsible for rendering the Quiet Mode page view with the appropriate layout.
* The Quiet Mode feature allows users to temporarily disable notifications and updates.
*/
class QuietModePage extends Component
{
/**
* Render the Quiet Mode page component.
*
* This method returns the view for the Quiet Mode page, using the 'account-app' layout.
* The view contains controls for managing the user's Quiet Mode settings.
*
* @return View The rendered view for the Quiet Mode page
*/
public function render(): View
{
return view('livewire.profile.quiet-mode-page')
->layout('components.layouts.account-app');
}
}
58 changes: 58 additions & 0 deletions app/Mail/User/QuietModeExpiredMail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace App\Mail\User;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;

/**
* Class QuietModeExpiredMail
*
* This mailable is responsible for sending an email notification to users
* when their Quiet Mode period has expired and been automatically deactivated.
*/
class QuietModeExpiredMail extends Mailable implements ShouldQueue
{
use Queueable;
use SerializesModels;

public function __construct(public readonly User $user)
{
//
}

/**
* Get the message envelope.
*
* This method defines the subject line and any additional headers for the email.
*/
public function envelope(): Envelope
{
return new Envelope(
subject: __('Welcome back! Your Quiet Mode period has ended'),
);
}

/**
* Get the message content definition.
*
* This method specifies the view to be used for the email content
* and any data that should be passed to that view.
*/
public function content(): Content
{
return new Content(
markdown: 'mail.user.quiet-mode-expired-mail',
with: [
'first_name' => $this->user->getAttribute('first_name'),
],
);
}
}
4 changes: 4 additions & 0 deletions app/Models/BackupTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,10 @@ public function sendNotifications(): void
*/
public function shouldSendNotification(NotificationStream $notificationStream, bool $backupTaskSuccessful): bool
{
if ($notificationStream->getAttribute('user') && $notificationStream->getAttribute('user')->hasQuietMode()) {
return false;
}

if ($backupTaskSuccessful && $notificationStream->hasSuccessfulBackupNotificationsEnabled()) {
return true;
}
Expand Down
32 changes: 32 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,37 @@ public function scopeWithOutdatedBackupCodes(Builder $builder): Builder
});
}

/**
* Returns whether the user has quiet mode enabled.
*/
public function hasQuietMode(): bool
{
return $this->quiet_until !== null;
}

/**
* Scope query to users that have a quiet mode enabled.
*
* @param Builder<User> $builder
* @return Builder<User>
*/
public function scopeWithQuietMode(Builder $builder): Builder
{
return $builder->whereNotNull('quiet_until');
}

/**
* It clears a user's quiet mode if set.
*/
public function clearQuietMode(): void
{
if (! $this->hasQuietMode()) {
return;
}

$this->forceFill(['quiet_until' => null])->save();
}

/**
* Get the casts array.
*
Expand All @@ -295,6 +326,7 @@ protected function casts(): array
'password' => 'hashed',
'weekly_summary_opt_in_at' => 'datetime',
'last_two_factor_at' => 'datetime',
'quiet_until' => 'datetime',
];
}

Expand Down
8 changes: 8 additions & 0 deletions database/factories/UserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function definition(): array
'pagination_count' => '25',
'language' => 'en',
'weekly_summary_opt_in_at' => now(),
'quiet_until' => null,
];
}

Expand All @@ -45,4 +46,11 @@ public function doesNotReceiveWeeklySummaries(): static
'weekly_summary_opt_in_at' => null,
]);
}

public function quietMode(): static
{
return $this->state(fn (array $attributes) => [
'quiet_until' => now(),
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dateTime('quiet_until')->nullable()->after('remember_token');
});
}
};
8 changes: 8 additions & 0 deletions resources/views/account/partials/sidebar.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@
</span>
</x-sidebar-nav-link>
</li>
<li class="flex-1 lg:flex-initial">
<x-sidebar-nav-link :href="route('profile.quiet-mode')" :active="request()->routeIs('profile.quiet-mode*')" wire:navigate>
<span class="flex flex-col lg:flex-row items-center justify-center lg:justify-start py-2 lg:py-1.5">
@svg('heroicon-o-bell-snooze', 'h-6 w-6 lg:h-5 lg:w-5 lg:mr-2')
<span class="text-xs mt-1 lg:mt-0 lg:text-sm">{{ __('Manage Quiet Mode') }}</span>
</span>
</x-sidebar-nav-link>
</li>
<li class="flex-1 lg:flex-initial">
<x-sidebar-nav-link :href="route('account.remove-account')" :active="request()->routeIs('account.remove-account')" wire:navigate>
<span class="flex flex-col lg:flex-row items-center justify-center lg:justify-start py-2 lg:py-1.5">
Expand Down
1 change: 1 addition & 0 deletions resources/views/layouts/app.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
:dismissible="$flashMessage['dismissible']"
/>
@endif
@include('partials.quiet-mode-banner')
{{ Breadcrumbs::render() }}
<!-- Page Heading -->
@if (isset($header))
Expand Down
Loading

0 comments on commit 59ed941

Please sign in to comment.