Skip to content

Commit adf270a

Browse files
simonhampclaude
andcommitted
Show Discord integration to Ultra subscribers and rename role to "Ultra"
Widens the Discord role gate from `hasMaxAccess()` to also include `hasUltraAccess()` so Ultra subscribers see the integration banner and can claim the role. Renames the role end-to-end (config key, env var, DiscordApi methods, jobs, Livewire state, user-facing copy) from "Max" to "Ultra" to match the new branding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9b78de5 commit adf270a

12 files changed

Lines changed: 207 additions & 62 deletions

app/Console/Commands/RemoveExpiredDiscordRoles.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class RemoveExpiredDiscordRoles extends Command
1010
{
1111
protected $signature = 'discord:remove-expired-roles';
1212

13-
protected $description = 'Remove Discord Max role for users whose Max licenses have expired';
13+
protected $description = 'Remove Discord Ultra role for users whose Max licenses or Ultra subscriptions have ended';
1414

1515
public function handle(): int
1616
{
@@ -23,8 +23,8 @@ public function handle(): int
2323
->get();
2424

2525
foreach ($users as $user) {
26-
if (! $user->hasMaxAccess()) {
27-
$success = $discord->removeMaxRole($user->discord_id);
26+
if (! $user->hasMaxAccess() && ! $user->hasUltraAccess()) {
27+
$success = $discord->removeUltraRole($user->discord_id);
2828

2929
if ($success) {
3030
$user->update([

app/Http/Controllers/DiscordIntegrationController.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,23 +70,23 @@ public function handleCallback(): RedirectResponse
7070

7171
if (! $discord->isGuildMember($discordUser['id'])) {
7272
return to_route('customer.integrations')
73-
->with('warning', 'Discord account connected! Please join the NativePHP Discord server to receive the Max role.');
73+
->with('warning', 'Discord account connected! Please join the NativePHP Discord server to receive the Ultra role.');
7474
}
7575

76-
if ($user->hasMaxAccess()) {
77-
$success = $discord->assignMaxRole($discordUser['id']);
76+
if ($user->hasMaxAccess() || $user->hasUltraAccess()) {
77+
$success = $discord->assignUltraRole($discordUser['id']);
7878

7979
if ($success) {
8080
$user->update([
8181
'discord_role_granted_at' => now(),
8282
]);
8383

8484
return to_route('customer.integrations')
85-
->with('success', 'Discord account connected and Max role assigned!');
85+
->with('success', 'Discord account connected and Ultra role assigned!');
8686
}
8787

8888
return to_route('customer.integrations')
89-
->with('warning', 'Discord account connected, but we could not assign the Max role. Please try again later.');
89+
->with('warning', 'Discord account connected, but we could not assign the Ultra role. Please try again later.');
9090
}
9191

9292
return to_route('customer.integrations')
@@ -108,7 +108,7 @@ public function disconnect(): RedirectResponse
108108

109109
if ($user->discord_role_granted_at && $user->discord_id) {
110110
$discord = DiscordApi::make();
111-
$discord->removeMaxRole($user->discord_id);
111+
$discord->removeUltraRole($user->discord_id);
112112
}
113113

114114
$user->update([
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use Illuminate\Queue\SerializesModels;
1212
use Illuminate\Support\Facades\Log;
1313

14-
class AssignDiscordMaxRoleJob implements ShouldQueue
14+
class AssignDiscordUltraRoleJob implements ShouldQueue
1515
{
1616
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
1717

@@ -31,8 +31,8 @@ public function handle(): void
3131
return;
3232
}
3333

34-
if (! $this->user->hasMaxAccess()) {
35-
Log::info('Skipping Discord role assignment - user has no Max access', [
34+
if (! $this->user->hasMaxAccess() && ! $this->user->hasUltraAccess()) {
35+
Log::info('Skipping Discord role assignment - user has no Max or Ultra access', [
3636
'user_id' => $this->user->id,
3737
]);
3838

@@ -50,17 +50,17 @@ public function handle(): void
5050
return;
5151
}
5252

53-
$success = $discord->assignMaxRole($this->user->discord_id);
53+
$success = $discord->assignUltraRole($this->user->discord_id);
5454

5555
if ($success) {
5656
$this->user->update(['discord_role_granted_at' => now()]);
5757

58-
Log::info('Discord Max role assigned successfully', [
58+
Log::info('Discord Ultra role assigned successfully', [
5959
'user_id' => $this->user->id,
6060
'discord_id' => $this->user->discord_id,
6161
]);
6262
} else {
63-
Log::error('Failed to assign Discord Max role', [
63+
Log::error('Failed to assign Discord Ultra role', [
6464
'user_id' => $this->user->id,
6565
'discord_id' => $this->user->discord_id,
6666
]);
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use Illuminate\Queue\SerializesModels;
1212
use Illuminate\Support\Facades\Log;
1313

14-
class RemoveDiscordMaxRoleJob implements ShouldQueue
14+
class RemoveDiscordUltraRoleJob implements ShouldQueue
1515
{
1616
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
1717

@@ -33,17 +33,17 @@ public function handle(): void
3333

3434
$discord = DiscordApi::make();
3535

36-
$success = $discord->removeMaxRole($this->user->discord_id);
36+
$success = $discord->removeUltraRole($this->user->discord_id);
3737

3838
if ($success) {
3939
$this->user->update(['discord_role_granted_at' => null]);
4040

41-
Log::info('Discord Max role removed successfully', [
41+
Log::info('Discord Ultra role removed successfully', [
4242
'user_id' => $this->user->id,
4343
'discord_id' => $this->user->discord_id,
4444
]);
4545
} else {
46-
Log::warning('Failed to remove Discord Max role (user may not have role)', [
46+
Log::warning('Failed to remove Discord Ultra role (user may not have role)', [
4747
'user_id' => $this->user->id,
4848
'discord_id' => $this->user->discord_id,
4949
]);

app/Jobs/RevokeMaxAccessJob.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,17 @@ private function revokeDiscordRole(User $user): void
8282
}
8383

8484
$discord = DiscordApi::make();
85-
$success = $discord->removeMaxRole($user->discord_id);
85+
$success = $discord->removeUltraRole($user->discord_id);
8686

8787
if ($success) {
8888
$user->update(['discord_role_granted_at' => null]);
8989

90-
Log::info('Discord Max role revoked for user', [
90+
Log::info('Discord Ultra role revoked for user', [
9191
'user_id' => $user->id,
9292
'discord_id' => $user->discord_id,
9393
]);
9494
} else {
95-
Log::warning('Failed to revoke Discord Max role for user', [
95+
Log::warning('Failed to revoke Discord Ultra role for user', [
9696
'user_id' => $user->id,
9797
'discord_id' => $user->discord_id,
9898
]);

app/Listeners/StripeWebhookReceivedListener.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use App\Jobs\CreateUserFromStripeCustomer;
66
use App\Jobs\HandleInvoicePaidJob;
7-
use App\Jobs\RemoveDiscordMaxRoleJob;
7+
use App\Jobs\RemoveDiscordUltraRoleJob;
88
use App\Jobs\RevokeTeamUserAccessJob;
99
use App\Jobs\SuspendTeamJob;
1010
use App\Jobs\UnsuspendTeamJob;
@@ -75,7 +75,7 @@ private function handleSubscriptionDeleted(WebhookReceived $event): void
7575
return;
7676
}
7777

78-
$this->removeDiscordRoleIfNoMaxLicense($user);
78+
$this->removeDiscordRoleIfNoAccess($user);
7979

8080
dispatch(new SuspendTeamJob($user->id));
8181
dispatch(new RevokeTeamUserAccessJob($user->id));
@@ -101,7 +101,7 @@ private function handleSubscriptionUpdated(WebhookReceived $event): void
101101
}
102102

103103
if (in_array($status, ['canceled', 'unpaid', 'past_due', 'incomplete_expired'])) {
104-
$this->removeDiscordRoleIfNoMaxLicense($user);
104+
$this->removeDiscordRoleIfNoAccess($user);
105105
dispatch(new SuspendTeamJob($user->id));
106106
dispatch(new RevokeTeamUserAccessJob($user->id));
107107
}
@@ -112,16 +112,16 @@ private function handleSubscriptionUpdated(WebhookReceived $event): void
112112
}
113113
}
114114

115-
private function removeDiscordRoleIfNoMaxLicense(User $user): void
115+
private function removeDiscordRoleIfNoAccess(User $user): void
116116
{
117117
if (! $user->discord_id) {
118118
return;
119119
}
120120

121-
if ($user->hasMaxAccess()) {
121+
if ($user->hasMaxAccess() || $user->hasUltraAccess()) {
122122
return;
123123
}
124124

125-
dispatch(new RemoveDiscordMaxRoleJob($user));
125+
dispatch(new RemoveDiscordUltraRoleJob($user));
126126
}
127127
}

app/Livewire/DiscordAccessBanner.php

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class DiscordAccessBanner extends Component
1010
{
1111
public bool $inline = false;
1212

13-
public bool $hasMaxRole = false;
13+
public bool $hasUltraRole = false;
1414

1515
public bool $isGuildMember = false;
1616

@@ -25,7 +25,7 @@ public function checkRoleStatus(): void
2525
$user = auth()->user();
2626

2727
if (! $user || ! $user->discord_id) {
28-
$this->hasMaxRole = false;
28+
$this->hasUltraRole = false;
2929
$this->isGuildMember = false;
3030

3131
return;
@@ -38,14 +38,14 @@ public function checkRoleStatus(): void
3838

3939
return [
4040
'isGuildMember' => $discord->isGuildMember($user->discord_id),
41-
'hasMaxRole' => $discord->hasMaxRole($user->discord_id),
41+
'hasUltraRole' => $discord->hasUltraRole($user->discord_id),
4242
];
4343
});
4444

4545
$this->isGuildMember = $status['isGuildMember'];
46-
$this->hasMaxRole = $status['hasMaxRole'];
46+
$this->hasUltraRole = $status['hasUltraRole'];
4747

48-
if ($this->hasMaxRole && ! $user->discord_role_granted_at) {
48+
if ($this->hasUltraRole && ! $user->discord_role_granted_at) {
4949
$user->update(['discord_role_granted_at' => now()]);
5050
}
5151
}
@@ -61,7 +61,7 @@ public function refreshStatus(): void
6161
$this->checkRoleStatus();
6262
}
6363

64-
public function requestMaxRole(): void
64+
public function requestUltraRole(): void
6565
{
6666
$user = auth()->user();
6767

@@ -71,8 +71,8 @@ public function requestMaxRole(): void
7171
return;
7272
}
7373

74-
if (! $user->hasMaxAccess()) {
75-
session()->flash('error', 'You need an active Max license to receive the Max role.');
74+
if (! $user->hasMaxAccess() && ! $user->hasUltraAccess()) {
75+
session()->flash('error', 'You need an active Max license or Ultra subscription to receive the Ultra role.');
7676

7777
return;
7878
}
@@ -85,15 +85,15 @@ public function requestMaxRole(): void
8585
return;
8686
}
8787

88-
$success = $discord->assignMaxRole($user->discord_id);
88+
$success = $discord->assignUltraRole($user->discord_id);
8989

9090
if ($success) {
9191
$user->update(['discord_role_granted_at' => now()]);
9292
Cache::forget("discord_role_status_{$user->id}");
9393
$this->checkRoleStatus();
94-
session()->flash('success', 'Max role assigned successfully!');
94+
session()->flash('success', 'Ultra role assigned successfully!');
9595
} else {
96-
session()->flash('error', 'Failed to assign Max role. Please try again later.');
96+
session()->flash('error', 'Failed to assign Ultra role. Please try again later.');
9797
}
9898
}
9999

app/Support/DiscordApi.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ class DiscordApi
1212
public function __construct(
1313
private ?string $botToken,
1414
private ?string $guildId,
15-
private ?string $maxRoleId
15+
private ?string $ultraRoleId
1616
) {}
1717

1818
public static function make(): static
1919
{
2020
return new static(
2121
config('services.discord.bot_token', ''),
2222
config('services.discord.guild_id', ''),
23-
config('services.discord.max_role_id', '')
23+
config('services.discord.ultra_role_id', '')
2424
);
2525
}
2626

@@ -69,19 +69,19 @@ public function isGuildMember(string $discordUserId): bool
6969
return true;
7070
}
7171

72-
public function assignMaxRole(string $discordUserId): bool
72+
public function assignUltraRole(string $discordUserId): bool
7373
{
7474
$response = Http::withToken($this->botToken, 'Bot')
7575
->put(sprintf(
7676
'%s/guilds/%s/members/%s/roles/%s',
7777
self::BASE_URL,
7878
$this->guildId,
7979
$discordUserId,
80-
$this->maxRoleId
80+
$this->ultraRoleId
8181
));
8282

8383
if ($response->failed()) {
84-
Log::error('Failed to assign Discord Max role', [
84+
Log::error('Failed to assign Discord Ultra role', [
8585
'discord_user_id' => $discordUserId,
8686
'status' => $response->status(),
8787
'response' => $response->json(),
@@ -93,19 +93,19 @@ public function assignMaxRole(string $discordUserId): bool
9393
return true;
9494
}
9595

96-
public function removeMaxRole(string $discordUserId): bool
96+
public function removeUltraRole(string $discordUserId): bool
9797
{
9898
$response = Http::withToken($this->botToken, 'Bot')
9999
->delete(sprintf(
100100
'%s/guilds/%s/members/%s/roles/%s',
101101
self::BASE_URL,
102102
$this->guildId,
103103
$discordUserId,
104-
$this->maxRoleId
104+
$this->ultraRoleId
105105
));
106106

107107
if ($response->failed()) {
108-
Log::error('Failed to remove Discord Max role', [
108+
Log::error('Failed to remove Discord Ultra role', [
109109
'discord_user_id' => $discordUserId,
110110
'status' => $response->status(),
111111
'response' => $response->json(),
@@ -117,7 +117,7 @@ public function removeMaxRole(string $discordUserId): bool
117117
return true;
118118
}
119119

120-
public function hasMaxRole(string $discordUserId): bool
120+
public function hasUltraRole(string $discordUserId): bool
121121
{
122122
$response = Http::withToken($this->botToken, 'Bot')
123123
->get(sprintf(
@@ -140,6 +140,6 @@ public function hasMaxRole(string $discordUserId): bool
140140
$member = $response->json();
141141
$roles = $member['roles'] ?? [];
142142

143-
return in_array($this->maxRoleId, $roles, true);
143+
return in_array($this->ultraRoleId, $roles, true);
144144
}
145145
}

config/services.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
'redirect' => env('APP_URL').'/auth/discord/callback',
5757
'bot_token' => env('DISCORD_BOT_TOKEN'),
5858
'guild_id' => env('DISCORD_GUILD_ID'),
59-
'max_role_id' => env('DISCORD_MAX_ROLE_ID'),
59+
'ultra_role_id' => env('DISCORD_ULTRA_ROLE_ID'),
6060
],
6161

6262
'turnstile' => [

resources/views/livewire/customer/integrations.blade.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<div class="mt-4 prose dark:prose-invert prose-sm max-w-none">
3030
<ul class="list-disc list-inside space-y-2">
3131
<li><strong>GitHub:</strong> Max license holders can access the private <code>nativephp/mobile</code> repository. Plugin Dev Kit license holders and Ultra subscribers can access <code>nativephp/claude-code</code>.</li>
32-
<li><strong>Discord:</strong> Max license holders receive a special "Max" role in the NativePHP Discord server.</li>
32+
<li><strong>Discord:</strong> Max license holders and Ultra subscribers receive a special "Ultra" role in the NativePHP Discord server.</li>
3333
</ul>
3434
<p class="mt-4">
3535
Need help? Join our <a href="https://discord.gg/nativephp" target="_blank" class="text-blue-600 hover:underline dark:text-blue-400">Discord community</a>.
@@ -47,7 +47,7 @@
4747
<livewire:git-hub-access-banner :inline="true" />
4848
@endif
4949

50-
@if(auth()->user()->hasMaxAccess())
50+
@if(auth()->user()->hasMaxAccess() || auth()->user()->hasUltraAccess())
5151
<livewire:discord-access-banner :inline="true" />
5252
@endif
5353
</div>

0 commit comments

Comments
 (0)