Skip to content

Commit 5e15733

Browse files
committed
Merge remote-tracking branch 'origin/main' into skip-unverified-emails
# Conflicts: # app/Listeners/SuppressMailNotificationListener.php # package-lock.json
2 parents 56863f4 + 6d6c738 commit 5e15733

19 files changed

Lines changed: 787 additions & 2854 deletions

app/Filament/Resources/UserResource.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace App\Filament\Resources;
44

5-
use App\Enums\StripeConnectStatus;
65
use App\Filament\Resources\UserResource\Pages;
76
use App\Filament\Resources\UserResource\RelationManagers;
87
use App\Models\User;
@@ -38,11 +37,11 @@ public static function form(Schema $schema): Schema
3837
->email()
3938
->required()
4039
->maxLength(255),
41-
Forms\Components\DateTimePicker::make('email_verified_at'),
4240
Forms\Components\TextInput::make('password')
4341
->password()
4442
->dehydrated(fn ($state) => filled($state))
4543
->required(fn (string $context): bool => $context === 'create')
44+
->hidden(fn (string $context): bool => $context === 'edit')
4645
->maxLength(255),
4746
]),
4847
Schemas\Components\Section::make('Billing Information')
@@ -64,15 +63,26 @@ public static function form(Schema $schema): Schema
6463
->maxLength(255)
6564
->disabled(),
6665
]),
66+
Schemas\Components\Section::make('Notifications')
67+
->description('Once these are disabled, they cannot be re-enabled by an admin.')
68+
->inlineLabel()
69+
->columns(1)
70+
->schema([
71+
Forms\Components\Toggle::make('receives_notification_emails')
72+
->label('Email notifications')
73+
->disabled(fn (?User $record) => $record && ! $record->receives_notification_emails),
74+
Forms\Components\Toggle::make('receives_new_plugin_notifications')
75+
->label('New plugin notifications')
76+
->disabled(fn (?User $record) => $record && ! $record->receives_new_plugin_notifications),
77+
]),
6778
Schemas\Components\Section::make('Developer Account')
6879
->inlineLabel()
6980
->columns(1)
7081
->visible(fn (?User $record) => $record?->developerAccount !== null)
7182
->schema([
72-
Forms\Components\Select::make('developerAccount.stripe_connect_status')
83+
Forms\Components\Placeholder::make('developerAccount.stripe_connect_status')
7384
->label('Stripe Connect Status')
74-
->options(StripeConnectStatus::class)
75-
->disabled(),
85+
->content(fn (User $record) => $record->developerAccount->stripe_connect_status?->label() ?? ''),
7686
Forms\Components\Placeholder::make('developerAccount.stripe_connect_account_id')
7787
->label('Stripe Connect Account')
7888
->content(fn (User $record) => new HtmlString(

app/Http/Controllers/Auth/CustomerAuthController.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\Models\TeamUser;
1010
use App\Models\User;
1111
use App\Services\CartService;
12+
use Illuminate\Auth\Events\Registered;
1213
use Illuminate\Auth\Passwords\PasswordBroker;
1314
use Illuminate\Http\RedirectResponse;
1415
use Illuminate\Http\Request;
@@ -45,6 +46,8 @@ public function register(Request $request): RedirectResponse
4546
'password' => Hash::make($request->password),
4647
]);
4748

49+
event(new Registered($user));
50+
4851
Auth::login($user);
4952

5053
// Transfer guest cart to user
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Auth;
4+
5+
use App\Http\Controllers\Controller;
6+
use Illuminate\Foundation\Auth\EmailVerificationRequest;
7+
use Illuminate\Http\RedirectResponse;
8+
use Illuminate\Http\Request;
9+
10+
class EmailVerificationController extends Controller
11+
{
12+
public function notice(): RedirectResponse
13+
{
14+
return to_route('dashboard');
15+
}
16+
17+
public function verify(EmailVerificationRequest $request): RedirectResponse
18+
{
19+
$request->fulfill();
20+
21+
return to_route('dashboard')->with('success', 'Your email address has been verified.');
22+
}
23+
24+
public function resend(Request $request): RedirectResponse
25+
{
26+
$request->user()->sendEmailVerificationNotification();
27+
28+
return back()->with('status', 'A new verification link has been sent to your email address.');
29+
}
30+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Models\User;
6+
use Illuminate\Http\RedirectResponse;
7+
use Illuminate\Http\Request;
8+
use Illuminate\View\View;
9+
10+
class NotificationUnsubscribeController extends Controller
11+
{
12+
public function unsubscribe(Request $request, User $user): RedirectResponse|View
13+
{
14+
$user->update(['receives_new_plugin_notifications' => false]);
15+
16+
if ($request->user()?->is($user)) {
17+
return redirect()
18+
->route('customer.settings', ['tab' => 'notifications'])
19+
->with('new-plugin-notifications-disabled', true);
20+
}
21+
22+
return view('notifications.unsubscribed', [
23+
'maskedEmail' => $this->maskEmail($user->email),
24+
'resubscribeUrl' => $this->signedResubscribeUrl($user),
25+
]);
26+
}
27+
28+
public function resubscribe(Request $request, User $user): RedirectResponse|View
29+
{
30+
$user->update(['receives_new_plugin_notifications' => true]);
31+
32+
if ($request->user()?->is($user)) {
33+
return redirect()
34+
->route('customer.settings', ['tab' => 'notifications'])
35+
->with('new-plugin-notifications-enabled', true);
36+
}
37+
38+
return view('notifications.resubscribed', [
39+
'maskedEmail' => $this->maskEmail($user->email),
40+
]);
41+
}
42+
43+
public static function signedUnsubscribeUrl(User $user): string
44+
{
45+
return url()->signedRoute('notifications.unsubscribe', ['user' => $user]);
46+
}
47+
48+
private function signedResubscribeUrl(User $user): string
49+
{
50+
return url()->signedRoute('notifications.resubscribe', ['user' => $user]);
51+
}
52+
53+
private function maskEmail(string $email): string
54+
{
55+
[$local, $domain] = explode('@', $email);
56+
57+
if (strlen($local) <= 2) {
58+
$maskedLocal = $local[0].str_repeat('*', max(1, strlen($local) - 1));
59+
} else {
60+
$maskedLocal = $local[0].str_repeat('*', strlen($local) - 2).$local[strlen($local) - 1];
61+
}
62+
63+
return $maskedLocal.'@'.$domain;
64+
}
65+
}

app/Jobs/SyncPluginReleases.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,17 @@ public function handle(SatisService $satisService): void
7878
}
7979
}
8080

81-
$this->plugin->update(['last_synced_at' => now()]);
81+
$updateData = ['last_synced_at' => now()];
82+
83+
if ($this->hasNewReleases) {
84+
$latestVersion = $this->plugin->versions()->latest('published_at')->first();
85+
86+
if ($latestVersion) {
87+
$updateData['latest_version'] = $latestVersion->version;
88+
}
89+
}
90+
91+
$this->plugin->update($updateData);
8292

8393
Log::info('[SyncPluginReleases] Processing complete', [
8494
'plugin_id' => $this->plugin->id,

app/Listeners/SuppressMailNotificationListener.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace App\Listeners;
44

55
use App\Models\User;
6+
use Illuminate\Auth\Notifications\VerifyEmail;
67
use Illuminate\Notifications\Events\NotificationSending;
78

89
class SuppressMailNotificationListener
@@ -17,10 +18,15 @@ public function handle(NotificationSending $event): bool
1718
return true;
1819
}
1920

21+
// System notifications like email verification should always be sent
22+
if ($event->notification instanceof VerifyEmail) {
23+
return true;
24+
}
25+
2026
if (! $event->notifiable->email_verified_at) {
2127
return false;
2228
}
2329

24-
return $event->notifiable->receives_notification_emails;
30+
return (bool) $event->notifiable->receives_notification_emails;
2531
}
2632
}

app/Models/User.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
namespace App\Models;
44

5-
// use Illuminate\Contracts\Auth\MustVerifyEmail;
65
use App\Enums\PriceTier;
76
use App\Enums\Subscription;
87
use App\Enums\TeamUserStatus;
98
use Filament\Models\Contracts\FilamentUser;
109
use Filament\Models\Contracts\HasName;
1110
use Filament\Panel;
11+
use Illuminate\Contracts\Auth\MustVerifyEmail;
1212
use Illuminate\Database\Eloquent\Casts\Attribute;
1313
use Illuminate\Database\Eloquent\Factories\HasFactory;
1414
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -19,7 +19,7 @@
1919
use Laravel\Cashier\Billable;
2020
use Laravel\Sanctum\HasApiTokens;
2121

22-
class User extends Authenticatable implements FilamentUser, HasName
22+
class User extends Authenticatable implements FilamentUser, HasName, MustVerifyEmail
2323
{
2424
use Billable, HasApiTokens, HasFactory, Notifiable;
2525

app/Notifications/NewPluginAvailable.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
namespace App\Notifications;
44

5+
use App\Http\Controllers\NotificationUnsubscribeController;
56
use App\Models\Plugin;
7+
use App\Models\User;
68
use Illuminate\Bus\Queueable;
79
use Illuminate\Contracts\Queue\ShouldQueue;
810
use Illuminate\Notifications\Messages\MailMessage;
@@ -30,12 +32,15 @@ public function via(object $notifiable): array
3032

3133
public function toMail(object $notifiable): MailMessage
3234
{
35+
/** @var User $notifiable */
36+
$unsubscribeUrl = NotificationUnsubscribeController::signedUnsubscribeUrl($notifiable);
37+
3338
return (new MailMessage)
3439
->subject("New Plugin: {$this->plugin->name}")
3540
->greeting('A new plugin is available!')
3641
->line("**{$this->plugin->name}** has just been added to the NativePHP Plugin Marketplace.")
3742
->action('View Plugin', route('plugins.show', $this->plugin->routeParams()))
38-
->line('[Manage your notification preferences]('.route('customer.settings', ['tab' => 'notifications']).').');
43+
->line('[Unsubscribe from new plugin notifications]('.$unsubscribeUrl.').');
3944
}
4045

4146
/**

0 commit comments

Comments
 (0)