From cd2798fd8daba593b016d8be8f0014a3e856b661 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Thu, 25 Jun 2026 13:54:16 -0300 Subject: [PATCH] refactor(social): use native Inertia for all connect popup flows Replace the per-platform native form POST + manual CSRF + JSON/Blade popup callback with a single Inertia mechanism: - popupCallback() renders the accounts/PopupCallback Inertia page (notifies the opener + closes the popup) for both the GET OAuth callbacks and the selection submits. Drops the auth.social-callback Blade view, the expectsJson JSON branch, and useHttp/useSocialConnect on the frontend. - Selection/credential pages (LinkedIn, Facebook, Instagram, Bluesky, Mastodon) use Inertia useForm: automatic CSRF + native validation errors. - Unify the three selection screens on one row + View/Choose layout; the LinkedIn company tag now uses a building icon. - Bluesky auth failures throw ValidationException (422 for XHR, redirect back with errors otherwise). Controller tests updated from assertViewIs/assertViewHas to assertInertia. --- .../Controllers/Auth/BlueskyController.php | 10 +- .../Controllers/Auth/DiscordController.php | 4 +- .../Controllers/Auth/FacebookController.php | 6 +- .../Controllers/Auth/InstagramController.php | 4 +- .../Auth/InstagramFacebookController.php | 8 +- .../Controllers/Auth/LinkedInController.php | 9 +- .../Controllers/Auth/MastodonController.php | 3 +- .../Controllers/Auth/PinterestController.php | 4 +- .../Controllers/Auth/SocialController.php | 11 ++- .../Controllers/Auth/ThreadsController.php | 4 +- .../Controllers/Auth/TikTokController.php | 4 +- app/Http/Controllers/Auth/XController.php | 4 +- .../Controllers/Auth/YouTubeController.php | 6 +- lang/en/accounts.php | 5 +- lang/es/accounts.php | 5 +- lang/pt-BR/accounts.php | 5 +- .../js/pages/accounts/BlueskyConnect.vue | 48 ++++------ .../js/pages/accounts/FacebookPageSelect.vue | 92 +++++++++---------- .../accounts/InstagramFacebookPageSelect.vue | 83 ++++++++--------- .../js/pages/accounts/LinkedInSelect.vue | 43 +++------ .../js/pages/accounts/MastodonConnect.vue | 44 +++------ resources/js/pages/accounts/PopupCallback.vue | 54 +++++++++++ .../views/auth/social-callback.blade.php | 89 ------------------ .../Feature/Social/BlueskyControllerTest.php | 7 +- .../Feature/Social/DiscordControllerTest.php | 5 +- .../Feature/Social/FacebookControllerTest.php | 35 +++---- .../Social/InstagramControllerTest.php | 21 +++-- .../InstagramFacebookControllerTest.php | 11 ++- .../Feature/Social/LinkedInControllerTest.php | 42 ++++----- .../Feature/Social/MastodonControllerTest.php | 13 +-- .../Social/PinterestControllerTest.php | 15 +-- .../Feature/Social/ThreadsControllerTest.php | 25 ++--- tests/Feature/Social/TikTokControllerTest.php | 15 +-- tests/Feature/Social/XControllerTest.php | 15 +-- .../Feature/Social/YouTubeControllerTest.php | 31 ++++--- 35 files changed, 353 insertions(+), 427 deletions(-) create mode 100644 resources/js/pages/accounts/PopupCallback.vue delete mode 100644 resources/views/auth/social-callback.blade.php diff --git a/app/Http/Controllers/Auth/BlueskyController.php b/app/Http/Controllers/Auth/BlueskyController.php index 6b3630b9..8f64c483 100644 --- a/app/Http/Controllers/Auth/BlueskyController.php +++ b/app/Http/Controllers/Auth/BlueskyController.php @@ -11,7 +11,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; -use Illuminate\View\View; +use Illuminate\Validation\ValidationException; use Inertia\Inertia; use Inertia\Response; @@ -36,7 +36,7 @@ public function connect(Request $request): Response|RedirectResponse ]); } - public function store(Request $request): View|RedirectResponse + public function store(Request $request): Response|RedirectResponse { $this->ensurePlatformEnabled(); @@ -74,7 +74,7 @@ public function store(Request $request): View|RedirectResponse $errorMessage = $body['message']; } - return back()->withErrors(['password' => $errorMessage]); + throw ValidationException::withMessages(['password' => $errorMessage]); } $data = $response->json(); @@ -113,13 +113,15 @@ public function store(Request $request): View|RedirectResponse ); return $this->popupCallback(true, __('accounts.popup_callback.connected'), $this->platform->value); + } catch (ValidationException $e) { + throw $e; } catch (\Exception $e) { Log::error('Bluesky connection error', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); - return back()->withErrors(['password' => 'Error connecting to Bluesky. Please try again.']); + throw ValidationException::withMessages(['password' => 'Error connecting to Bluesky. Please try again.']); } } } diff --git a/app/Http/Controllers/Auth/DiscordController.php b/app/Http/Controllers/Auth/DiscordController.php index 87ff8c25..acc6acd9 100644 --- a/app/Http/Controllers/Auth/DiscordController.php +++ b/app/Http/Controllers/Auth/DiscordController.php @@ -7,7 +7,7 @@ use App\Enums\SocialAccount\Platform as SocialPlatform; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\View\View; +use Inertia\Response as InertiaResponse; use Symfony\Component\HttpFoundation\Response; class DiscordController extends SocialController @@ -31,7 +31,7 @@ public function connect(Request $request): Response|RedirectResponse return $this->redirectToProvider($request, $this->driver, config('trypost.platforms.discord.scopes')); } - public function callback(Request $request): View + public function callback(Request $request): InertiaResponse { return $this->handleCallback($request, $this->platform, $this->driver); } diff --git a/app/Http/Controllers/Auth/FacebookController.php b/app/Http/Controllers/Auth/FacebookController.php index 90d8b3de..5021bbad 100644 --- a/app/Http/Controllers/Auth/FacebookController.php +++ b/app/Http/Controllers/Auth/FacebookController.php @@ -13,8 +13,8 @@ use Illuminate\Support\Arr; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; -use Illuminate\View\View; use Inertia\Inertia; +use Inertia\Response as InertiaResponse; use Laravel\Socialite\Facades\Socialite; use Symfony\Component\HttpFoundation\Response; @@ -58,7 +58,7 @@ public function connect(Request $request): Response|RedirectResponse ); } - public function callback(Request $request): View|RedirectResponse + public function callback(Request $request): InertiaResponse|RedirectResponse { $workspaceId = session('social_connect_workspace'); @@ -174,7 +174,7 @@ public function selectPage(Request $request) ]); } - public function select(Request $request): View + public function select(Request $request): InertiaResponse { $request->validate([ 'page_id' => 'required|string', diff --git a/app/Http/Controllers/Auth/InstagramController.php b/app/Http/Controllers/Auth/InstagramController.php index 2fddbe2a..42ecf396 100644 --- a/app/Http/Controllers/Auth/InstagramController.php +++ b/app/Http/Controllers/Auth/InstagramController.php @@ -11,8 +11,8 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; -use Illuminate\View\View; use Inertia\Inertia; +use Inertia\Response as InertiaResponse; use Laravel\Socialite\Facades\Socialite; use Symfony\Component\HttpFoundation\Response; @@ -53,7 +53,7 @@ public function connect(Request $request): Response|RedirectResponse return Inertia::location($url); } - public function callback(Request $request): View + public function callback(Request $request): InertiaResponse { $workspaceId = session('social_connect_workspace'); diff --git a/app/Http/Controllers/Auth/InstagramFacebookController.php b/app/Http/Controllers/Auth/InstagramFacebookController.php index 57eef9ad..5aa6224e 100644 --- a/app/Http/Controllers/Auth/InstagramFacebookController.php +++ b/app/Http/Controllers/Auth/InstagramFacebookController.php @@ -13,8 +13,8 @@ use Illuminate\Support\Arr; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; -use Illuminate\View\View; use Inertia\Inertia; +use Inertia\Response as InertiaResponse; use Laravel\Socialite\Facades\Socialite; use Symfony\Component\HttpFoundation\Response; @@ -61,7 +61,7 @@ public function connect(Request $request): Response|RedirectResponse return Inertia::location($url); } - public function callback(Request $request): View|RedirectResponse + public function callback(Request $request): InertiaResponse|RedirectResponse { $workspaceId = session('social_connect_workspace'); @@ -150,7 +150,7 @@ public function selectPage(Request $request) ]); } - public function select(Request $request): View + public function select(Request $request): InertiaResponse { $request->validate([ 'page_id' => 'required|string', @@ -193,7 +193,7 @@ public function select(Request $request): View } } - private function connectInstagramAccount(Workspace $workspace, array $pageData, $existingAccount): View + private function connectInstagramAccount(Workspace $workspace, array $pageData, $existingAccount): InertiaResponse { $avatarPath = data_get($pageData, 'ig_picture') ? uploadFromUrl(data_get($pageData, 'ig_picture')) : null; diff --git a/app/Http/Controllers/Auth/LinkedInController.php b/app/Http/Controllers/Auth/LinkedInController.php index 2fcfa6cc..daf49524 100644 --- a/app/Http/Controllers/Auth/LinkedInController.php +++ b/app/Http/Controllers/Auth/LinkedInController.php @@ -15,7 +15,6 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rules\Enum; -use Illuminate\View\View; use Inertia\Inertia; use Inertia\Response as InertiaResponse; use Laravel\Socialite\Facades\Socialite; @@ -39,7 +38,7 @@ protected function ensurePlatformEnabled(): void } } - public function connect(Request $request): Response|View + public function connect(Request $request): Response|InertiaResponse { $this->ensurePlatformEnabled(); @@ -61,7 +60,7 @@ public function connect(Request $request): Response|View ); } - public function callback(Request $request): View|RedirectResponse + public function callback(Request $request): InertiaResponse|RedirectResponse { $workspaceId = session('social_connect_workspace'); @@ -105,7 +104,7 @@ public function callback(Request $request): View|RedirectResponse } } - public function selectIdentity(Request $request): InertiaResponse|View + public function selectIdentity(Request $request): InertiaResponse { $pending = session('linkedin_pending'); @@ -125,7 +124,7 @@ public function selectIdentity(Request $request): InertiaResponse|View ]); } - public function select(Request $request): View + public function select(Request $request): InertiaResponse { $validator = Validator::make($request->all(), [ 'type' => ['required', new Enum(LinkedInIdentityType::class)], diff --git a/app/Http/Controllers/Auth/MastodonController.php b/app/Http/Controllers/Auth/MastodonController.php index d42367b6..e980beea 100644 --- a/app/Http/Controllers/Auth/MastodonController.php +++ b/app/Http/Controllers/Auth/MastodonController.php @@ -11,7 +11,6 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; -use Illuminate\View\View; use Inertia\Inertia; use Inertia\Response; use Symfony\Component\HttpFoundation\Response as SymfonyResponse; @@ -117,7 +116,7 @@ public function authorizeInstance(Request $request): SymfonyResponse|RedirectRes /** * Handle OAuth callback */ - public function callback(Request $request): View + public function callback(Request $request): Response { $workspaceId = session('social_connect_workspace'); $savedState = session('mastodon_oauth_state'); diff --git a/app/Http/Controllers/Auth/PinterestController.php b/app/Http/Controllers/Auth/PinterestController.php index d82eeedb..ea8555c4 100644 --- a/app/Http/Controllers/Auth/PinterestController.php +++ b/app/Http/Controllers/Auth/PinterestController.php @@ -10,7 +10,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; -use Illuminate\View\View; +use Inertia\Response as InertiaResponse; use Laravel\Socialite\Facades\Socialite; use Symfony\Component\HttpFoundation\Response; @@ -43,7 +43,7 @@ public function connect(Request $request): Response|RedirectResponse return $this->redirectToProvider($request, $this->driver, $this->scopes); } - public function callback(Request $request): View + public function callback(Request $request): InertiaResponse { $workspaceId = session('social_connect_workspace'); diff --git a/app/Http/Controllers/Auth/SocialController.php b/app/Http/Controllers/Auth/SocialController.php index b049785d..bd7b66c9 100644 --- a/app/Http/Controllers/Auth/SocialController.php +++ b/app/Http/Controllers/Auth/SocialController.php @@ -16,7 +16,6 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; -use Illuminate\View\View; use Inertia\Inertia; use Inertia\Response; use Laravel\Socialite\Facades\Socialite; @@ -135,7 +134,7 @@ protected function handleCallback( Request $request, SocialPlatform $platform, string $driver - ): View { + ): Response { $workspaceId = session('social_connect_workspace'); if (! $workspaceId) { @@ -191,13 +190,15 @@ protected function forgetSocialConnectSession(): void } /** - * Return a view that closes the popup and notifies the parent window. + * Render the Inertia page that notifies the opener and closes the connect + * popup. Used by both the GET OAuth callbacks (a fresh popup page load) and + * the XHR selection submits (an Inertia visit that swaps to this page). */ - protected function popupCallback(bool $success, string $message, ?string $platform = null): View + protected function popupCallback(bool $success, string $message, ?string $platform = null): Response { $this->forgetSocialConnectSession(); - return view('auth.social-callback', [ + return Inertia::render('accounts/PopupCallback', [ 'success' => $success, 'message' => $message, 'platform' => $platform, diff --git a/app/Http/Controllers/Auth/ThreadsController.php b/app/Http/Controllers/Auth/ThreadsController.php index 3455226e..e19165b9 100644 --- a/app/Http/Controllers/Auth/ThreadsController.php +++ b/app/Http/Controllers/Auth/ThreadsController.php @@ -12,8 +12,8 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; -use Illuminate\View\View; use Inertia\Inertia; +use Inertia\Response as InertiaResponse; use Symfony\Component\HttpFoundation\Response; class ThreadsController extends SocialController @@ -57,7 +57,7 @@ public function connect(Request $request): Response|RedirectResponse return Inertia::location("https://threads.net/oauth/authorize?{$params}"); } - public function callback(Request $request): View + public function callback(Request $request): InertiaResponse { $workspaceId = session('social_connect_workspace'); $savedState = session('threads_oauth_state'); diff --git a/app/Http/Controllers/Auth/TikTokController.php b/app/Http/Controllers/Auth/TikTokController.php index b2d49ecc..ac9b74e4 100644 --- a/app/Http/Controllers/Auth/TikTokController.php +++ b/app/Http/Controllers/Auth/TikTokController.php @@ -10,7 +10,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; -use Illuminate\View\View; +use Inertia\Response as InertiaResponse; use Laravel\Socialite\Facades\Socialite; use Symfony\Component\HttpFoundation\Response; @@ -46,7 +46,7 @@ public function connect(Request $request): Response|RedirectResponse return $this->redirectToProvider($request, $this->driver, $this->scopes); } - public function callback(Request $request): View + public function callback(Request $request): InertiaResponse { $workspaceId = session('social_connect_workspace'); diff --git a/app/Http/Controllers/Auth/XController.php b/app/Http/Controllers/Auth/XController.php index 9f3e3cd4..83cbe7c6 100644 --- a/app/Http/Controllers/Auth/XController.php +++ b/app/Http/Controllers/Auth/XController.php @@ -7,7 +7,7 @@ use App\Enums\SocialAccount\Platform as SocialPlatform; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\View\View; +use Inertia\Response as InertiaResponse; use Symfony\Component\HttpFoundation\Response; class XController extends SocialController @@ -39,7 +39,7 @@ public function connect(Request $request): Response|RedirectResponse return $this->redirectToProvider($request, $this->driver, $this->scopes); } - public function callback(Request $request): View + public function callback(Request $request): InertiaResponse { return $this->handleCallback($request, $this->platform, $this->driver); } diff --git a/app/Http/Controllers/Auth/YouTubeController.php b/app/Http/Controllers/Auth/YouTubeController.php index a1758a6b..7153fba3 100644 --- a/app/Http/Controllers/Auth/YouTubeController.php +++ b/app/Http/Controllers/Auth/YouTubeController.php @@ -12,8 +12,8 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; -use Illuminate\View\View; use Inertia\Inertia; +use Inertia\Response as InertiaResponse; use Laravel\Socialite\Facades\Socialite; use Symfony\Component\HttpFoundation\Response; @@ -50,7 +50,7 @@ public function connect(Request $request): Response|RedirectResponse return $this->redirectToGoogle(); } - public function callback(Request $request): View|RedirectResponse + public function callback(Request $request): InertiaResponse|RedirectResponse { $workspaceId = session('social_connect_workspace'); @@ -168,7 +168,7 @@ public function selectChannel(Request $request) ]); } - public function select(Request $request): View + public function select(Request $request): InertiaResponse { $request->validate([ 'channel_id' => 'required|string', diff --git a/lang/en/accounts.php b/lang/en/accounts.php index 880c0082..d3f0d5ee 100644 --- a/lang/en/accounts.php +++ b/lang/en/accounts.php @@ -83,6 +83,8 @@ 'no_pages' => 'No pages found', 'no_pages_description' => 'You are not an admin of any Facebook page.', 'page_label' => 'Facebook Page', + 'view' => 'View', + 'choose' => 'Choose', ], 'instagram_facebook' => [ @@ -90,6 +92,8 @@ 'description' => 'Choose which Instagram account you want to connect', 'no_pages' => 'No Instagram accounts found', 'no_pages_description' => 'No Facebook Pages with linked Instagram Business accounts were found.', + 'view' => 'View', + 'choose' => 'Choose', ], 'linkedin' => [ @@ -121,7 +125,6 @@ 'title_success' => 'Connected', 'title_error' => 'Error', 'closing' => 'This window will close automatically...', - 'close_now' => 'You can close this window now.', 'connected' => 'Account connected!', 'reconnected' => 'Account reconnected!', 'error_connecting' => 'Error connecting account. Please try again.', diff --git a/lang/es/accounts.php b/lang/es/accounts.php index 71f88021..e665a3e9 100644 --- a/lang/es/accounts.php +++ b/lang/es/accounts.php @@ -83,6 +83,8 @@ 'no_pages' => 'No se encontraron páginas', 'no_pages_description' => 'No eres administrador de ninguna página de Facebook.', 'page_label' => 'Página de Facebook', + 'view' => 'Ver', + 'choose' => 'Elegir', ], 'instagram_facebook' => [ @@ -90,6 +92,8 @@ 'description' => 'Elige qué cuenta de Instagram deseas conectar', 'no_pages' => 'No se encontraron cuentas de Instagram', 'no_pages_description' => 'No se encontraron páginas de Facebook con cuentas Instagram Business vinculadas.', + 'view' => 'Ver', + 'choose' => 'Elegir', ], 'linkedin' => [ @@ -121,7 +125,6 @@ 'title_success' => 'Conectado', 'title_error' => 'Error', 'closing' => 'Esta ventana se cerrará automáticamente...', - 'close_now' => 'Puedes cerrar esta ventana ahora.', 'connected' => '¡Cuenta conectada!', 'reconnected' => '¡Cuenta reconectada!', 'error_connecting' => 'Error al conectar la cuenta. Inténtalo de nuevo.', diff --git a/lang/pt-BR/accounts.php b/lang/pt-BR/accounts.php index 73ebee8b..dee22d18 100644 --- a/lang/pt-BR/accounts.php +++ b/lang/pt-BR/accounts.php @@ -83,6 +83,8 @@ 'no_pages' => 'Nenhuma página encontrada', 'no_pages_description' => 'Você não é administrador de nenhuma página do Facebook.', 'page_label' => 'Página do Facebook', + 'view' => 'Ver', + 'choose' => 'Escolher', ], 'instagram_facebook' => [ @@ -90,6 +92,8 @@ 'description' => 'Escolha qual conta do Instagram você deseja conectar', 'no_pages' => 'Nenhuma conta do Instagram encontrada', 'no_pages_description' => 'Nenhuma Página do Facebook com conta Instagram Business vinculada foi encontrada.', + 'view' => 'Ver', + 'choose' => 'Escolher', ], 'linkedin' => [ @@ -121,7 +125,6 @@ 'title_success' => 'Conectado', 'title_error' => 'Erro', 'closing' => 'Esta janela será fechada automaticamente...', - 'close_now' => 'Você pode fechar esta janela agora.', 'connected' => 'Conta conectada!', 'reconnected' => 'Conta reconectada!', 'error_connecting' => 'Erro ao conectar conta. Por favor, tente novamente.', diff --git a/resources/js/pages/accounts/BlueskyConnect.vue b/resources/js/pages/accounts/BlueskyConnect.vue index 4956fe05..3204f3b0 100644 --- a/resources/js/pages/accounts/BlueskyConnect.vue +++ b/resources/js/pages/accounts/BlueskyConnect.vue @@ -1,7 +1,7 @@ \ No newline at end of file + diff --git a/resources/js/pages/accounts/FacebookPageSelect.vue b/resources/js/pages/accounts/FacebookPageSelect.vue index f1218db5..85513db3 100644 --- a/resources/js/pages/accounts/FacebookPageSelect.vue +++ b/resources/js/pages/accounts/FacebookPageSelect.vue @@ -1,9 +1,9 @@