Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/Http/Controllers/GitHubIntegrationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Laravel\Socialite\Facades\Socialite;
Expand Down Expand Up @@ -206,6 +207,9 @@ public function disconnect(): RedirectResponse
'claude_plugins_repo_access_granted_at' => null,
]);

Cache::forget("github_collab_status_{$user->id}");
Cache::forget("github_claude_plugins_collab_status_{$user->id}");

return back()->with('success', 'GitHub account disconnected successfully.');
}

Expand Down
70 changes: 70 additions & 0 deletions resources/views/livewire/customer/integrations.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,76 @@
</div>
</flux:card>

{{-- GitHub Account --}}
<flux:card class="mb-6">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
<div class="flex items-start gap-4">
<svg class="h-6 w-6 flex-shrink-0 text-gray-700 dark:text-gray-300" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd" />
</svg>
<div>
<flux:heading>GitHub Account</flux:heading>
@if (auth()->user()->github_username)
<flux:text class="mt-1">Connected as <span class="font-mono font-medium text-gray-900 dark:text-white">{{ '@' . auth()->user()->github_username }}</span></flux:text>
@else
<flux:text class="mt-1">Connect your GitHub account to access private repositories and publish plugins.</flux:text>
@endif
</div>
</div>
<div class="lg:flex-shrink-0">
@if (auth()->user()->github_username)
<flux:modal.trigger name="disconnect-github">
<flux:button variant="danger">Disconnect</flux:button>
</flux:modal.trigger>
@else
<flux:button href="{{ route('github.redirect', ['return' => route('customer.integrations')]) }}">Connect GitHub</flux:button>
@endif
</div>
</div>
</flux:card>

{{-- Disconnect GitHub Confirmation Modal --}}
@if (auth()->user()->github_username)
<flux:modal name="disconnect-github" class="md:w-[32rem]">
<form action="{{ route('github.disconnect') }}" method="POST">
@csrf
@method('DELETE')

<flux:heading size="lg">Disconnect GitHub?</flux:heading>

<flux:text class="mt-2">
This will unlink <span class="font-mono font-medium">{{ '@' . auth()->user()->github_username }}</span> from your NativePHP account.
</flux:text>

<flux:callout variant="warning" icon="exclamation-triangle" class="mt-4">
<flux:callout.text>
<ul class="list-disc list-inside space-y-1">
<li>Any access to the private <code>nativephp/mobile</code> and <code>nativephp/claude-code</code> repositories will be revoked.</li>
<li>Your GitHub repositories will no longer be available when submitting or managing plugins.</li>
<li>You can reconnect at any time. When re-authorizing, be sure to grant access to any organizations whose repositories you need.</li>
</ul>
</flux:callout.text>
</flux:callout>

@if (! auth()->user()->password)
<flux:callout variant="danger" icon="exclamation-triangle" class="mt-4">
<flux:callout.text>
Your account uses GitHub to sign in and has no password set. Consider setting a password in
<a href="{{ route('customer.settings') }}" class="underline">Settings</a> before disconnecting.
</flux:callout.text>
</flux:callout>
@endif

<div class="mt-6 flex justify-end gap-3">
<flux:modal.close>
<flux:button type="button" variant="ghost">Cancel</flux:button>
</flux:modal.close>
<flux:button type="submit" variant="danger">Disconnect GitHub</flux:button>
</div>
</form>
</flux:modal>
@endif

{{-- Claude Plugins Access --}}
<div class="mb-6">
<livewire:claude-plugins-access-banner :inline="true" />
Expand Down
7 changes: 0 additions & 7 deletions resources/views/livewire/git-hub-access-banner.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,6 @@
</button>
</form>
@endif
<form action="{{ route('github.disconnect') }}" method="POST">
@csrf
@method('DELETE')
<button type="submit" class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
Disconnect
</button>
</form>
@else
<a href="{{ route('github.redirect') }}" class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-gray-900 dark:bg-white dark:text-gray-900 hover:bg-gray-800 dark:hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">
Connect GitHub
Expand Down
58 changes: 58 additions & 0 deletions tests/Feature/GitHubIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,64 @@ public function test_user_cannot_request_repo_access_without_connected_github():
$response->assertSessionHas('error', 'Please connect your GitHub account first.');
}

public function test_user_without_any_license_sees_github_account_card_with_connect_button(): void
{
Http::fake(['api.github.com/*' => Http::response([], 404)]);

$user = User::factory()->create();

$response = $this->actingAs($user)->get('/dashboard/integrations');

$response->assertStatus(200);
$response->assertSee('GitHub Account');
$response->assertSee('Connect GitHub');
}

public function test_connected_user_without_any_license_can_see_disconnect_option(): void
{
Http::fake(['api.github.com/*' => Http::response([], 404)]);

$user = User::factory()->create([
'github_username' => 'testuser',
'github_id' => '123456',
]);

$response = $this->actingAs($user)->get('/dashboard/integrations');

$response->assertStatus(200);
$response->assertSee('GitHub Account');
$response->assertSee('Connected as');
$response->assertSee('@testuser');
$response->assertSee('Disconnect GitHub?');
}

public function test_disconnect_clears_token_and_collaborator_status_caches(): void
{
Http::fake(['api.github.com/*' => Http::response([], 204)]);

$user = User::factory()->create([
'github_username' => 'testuser',
'github_id' => '123456',
'github_token' => encrypt('gho_test_token'),
]);

Cache::put("github_collab_status_{$user->id}", 'active', 300);
Cache::put("github_claude_plugins_collab_status_{$user->id}", 'active', 300);

$response = $this->actingAs($user)
->delete('/dashboard/github/disconnect');

$response->assertRedirect();
$response->assertSessionHas('success');

$user->refresh();
$this->assertNull($user->github_token);
$this->assertNull($user->github_username);
$this->assertNull($user->github_id);
$this->assertFalse(Cache::has("github_collab_status_{$user->id}"));
$this->assertFalse(Cache::has("github_claude_plugins_collab_status_{$user->id}"));
}

public function test_user_can_disconnect_github_account(): void
{
Http::fake([
Expand Down
Loading