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.
This commit is contained in:
parent
10b8579079
commit
cd2798fd8d
35 changed files with 353 additions and 427 deletions
|
|
@ -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.']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)],
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { IconInfoCircle } from '@tabler/icons-vue';
|
||||
import { trans } from 'laravel-vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -10,56 +10,40 @@ import { Label } from '@/components/ui/label';
|
|||
import PopupLayout from '@/layouts/PopupLayout.vue';
|
||||
import { store as storeBluesky } from '@/routes/app/social/bluesky';
|
||||
|
||||
interface Props {
|
||||
errors?: Record<string, string>;
|
||||
}
|
||||
const form = useForm({ identifier: '', password: '' });
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const formRef = ref<HTMLFormElement | null>(null);
|
||||
const identifier = ref('');
|
||||
const password = ref('');
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
const submit = () => {
|
||||
isSubmitting.value = true;
|
||||
formRef.value?.submit();
|
||||
};
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ?? '';
|
||||
const onSubmit = () => form.post(storeBluesky.url());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopupLayout :title="$t('accounts.bluesky.title')">
|
||||
<div class="max-w-md mx-auto">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<img src="/images/accounts/bluesky.png" alt="Bluesky" class="h-12 w-12" />
|
||||
<img src="/images/accounts/bluesky.png" alt="Bluesky" class="h-10 w-10" />
|
||||
<div>
|
||||
<h1 class="text-xl font-bold tracking-tight">{{ $t('accounts.bluesky.title') }}</h1>
|
||||
<p class="text-sm text-muted-foreground">{{ $t('accounts.bluesky.description') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form ref="formRef" :action="storeBluesky.url()" method="POST" @submit.prevent="submit" class="space-y-4">
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
|
||||
<form @submit.prevent="onSubmit" class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="identifier">{{ $t('accounts.bluesky.email') }}</Label>
|
||||
<Input id="identifier" name="identifier" v-model="identifier" type="text"
|
||||
:placeholder="trans('accounts.bluesky.email_placeholder')" :class="{ 'border-destructive': errors?.identifier }"
|
||||
<Input id="identifier" v-model="form.identifier" type="text"
|
||||
:placeholder="trans('accounts.bluesky.email_placeholder')" :class="{ 'border-destructive': form.errors.identifier }"
|
||||
/>
|
||||
<p v-if="errors?.identifier" class="text-sm text-destructive">
|
||||
{{ errors.identifier }}
|
||||
<p v-if="form.errors.identifier" class="text-sm text-destructive">
|
||||
{{ form.errors.identifier }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="password">{{ $t('accounts.bluesky.app_password') }}</Label>
|
||||
<Input id="password" name="password" v-model="password" type="password"
|
||||
:placeholder="trans('accounts.bluesky.app_password_placeholder')" :class="{ 'border-destructive': errors?.password }"
|
||||
<Input id="password" v-model="form.password" type="password"
|
||||
:placeholder="trans('accounts.bluesky.app_password_placeholder')" :class="{ 'border-destructive': form.errors.password }"
|
||||
/>
|
||||
<p v-if="errors?.password" class="text-sm text-destructive">
|
||||
{{ errors.password }}
|
||||
<p v-if="form.errors.password" class="text-sm text-destructive">
|
||||
{{ form.errors.password }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -70,10 +54,10 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Button type="submit" :disabled="isSubmitting" class="w-full">
|
||||
{{ isSubmitting ? $t('accounts.bluesky.submitting') : $t('accounts.bluesky.submit') }}
|
||||
<Button type="submit" :disabled="form.processing" class="w-full">
|
||||
{{ form.processing ? $t('accounts.bluesky.submitting') : $t('accounts.bluesky.submit') }}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</PopupLayout>
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import { IconBrandFacebook, IconCheck } from '@tabler/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { IconBrandFacebook, IconExternalLink } from '@tabler/icons-vue';
|
||||
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import PopupLayout from '@/layouts/PopupLayout.vue';
|
||||
import { select as selectFacebookPage } from '@/routes/app/social/facebook';
|
||||
|
||||
|
|
@ -23,30 +23,29 @@ interface Workspace {
|
|||
interface Props {
|
||||
workspace: Workspace;
|
||||
pages: Page[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const formRef = ref<HTMLFormElement | null>(null);
|
||||
const selectedPageId = ref<string | null>(null);
|
||||
const form = useForm({ page_id: '' });
|
||||
|
||||
const handleSelectPage = (page: Page) => {
|
||||
selectedPageId.value = page.id;
|
||||
setTimeout(() => formRef.value?.submit(), 0);
|
||||
form.page_id = page.id;
|
||||
form.post(selectFacebookPage.url());
|
||||
};
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ?? '';
|
||||
const openExternal = (url: string | null) => {
|
||||
if (url) {
|
||||
window.open(url, '_blank', 'noopener');
|
||||
}
|
||||
};
|
||||
|
||||
const pageUrl = (username: string | null): string | null =>
|
||||
username ? `https://www.facebook.com/${username}` : null;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopupLayout :title="$t('accounts.facebook.title')">
|
||||
<!-- Hidden form for regular POST submission -->
|
||||
<form ref="formRef" :action="selectFacebookPage.url()" method="POST" class="hidden">
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
<input type="hidden" name="page_id" :value="selectedPageId" />
|
||||
</form>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<img src="/images/accounts/facebook.png" alt="Facebook" class="h-10 w-10" />
|
||||
|
|
@ -56,11 +55,7 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Alert v-if="error" variant="destructive">
|
||||
<AlertDescription>{{ error }}</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div v-if="pages.length === 0 && !error" class="text-center py-12">
|
||||
<div v-if="pages.length === 0" class="py-12 text-center">
|
||||
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-muted">
|
||||
<IconBrandFacebook class="h-7 w-7 text-muted-foreground" />
|
||||
</div>
|
||||
|
|
@ -71,37 +66,40 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
</div>
|
||||
|
||||
<div v-else class="grid gap-3">
|
||||
<button
|
||||
<div
|
||||
v-for="page in pages"
|
||||
:key="page.id"
|
||||
@click="handleSelectPage(page)"
|
||||
class="group relative overflow-hidden rounded-lg border bg-card p-4 text-left transition-all hover:border-primary hover:shadow-md focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||
class="flex items-center gap-4 rounded-lg border bg-card p-4"
|
||||
dusk="facebook-page"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<Avatar class="h-12 w-12 rounded-lg">
|
||||
<AvatarImage v-if="page.picture" :src="page.picture" class="object-cover" />
|
||||
<AvatarFallback class="rounded-lg bg-blue-100 dark:bg-blue-900">
|
||||
<IconBrandFacebook class="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-semibold truncate group-hover:text-primary transition-colors">
|
||||
{{ page.name }}
|
||||
</h3>
|
||||
<p v-if="page.username" class="text-sm text-muted-foreground truncate">
|
||||
facebook.com/{{ page.username }}
|
||||
</p>
|
||||
<p v-else class="text-sm text-muted-foreground">{{ $t('accounts.facebook.page_label') }}</p>
|
||||
</div>
|
||||
<div class="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<div
|
||||
class="flex h-8 w-8 items-center justify-center rounded-full bg-primary text-primary-foreground"
|
||||
>
|
||||
<IconCheck class="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<Avatar class="h-12 w-12 rounded-lg">
|
||||
<AvatarImage v-if="page.picture" :src="page.picture" class="object-cover" />
|
||||
<AvatarFallback class="rounded-lg bg-blue-100 dark:bg-blue-900">
|
||||
<IconBrandFacebook class="h-6 w-6 text-blue-600 dark:text-blue-400" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="min-w-0 flex-1">
|
||||
<h3 class="truncate font-semibold">{{ page.name }}</h3>
|
||||
<p v-if="page.username" class="truncate text-sm text-muted-foreground">
|
||||
facebook.com/{{ page.username }}
|
||||
</p>
|
||||
<p v-else class="truncate text-sm text-muted-foreground">{{ $t('accounts.facebook.page_label') }}</p>
|
||||
</div>
|
||||
</button>
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
<Button
|
||||
v-if="pageUrl(page.username)"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="openExternal(pageUrl(page.username))"
|
||||
>
|
||||
<IconExternalLink class="h-4 w-4" />
|
||||
{{ $t('accounts.facebook.view') }}
|
||||
</Button>
|
||||
<Button size="sm" dusk="choose-facebook-page" :disabled="form.processing" @click="handleSelectPage(page)">
|
||||
{{ $t('accounts.facebook.choose') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopupLayout>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import { IconBrandInstagram, IconCheck } from '@tabler/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { IconBrandInstagram, IconExternalLink } from '@tabler/icons-vue';
|
||||
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import PopupLayout from '@/layouts/PopupLayout.vue';
|
||||
import { select as selectPage } from '@/routes/app/social/instagram-facebook';
|
||||
|
||||
|
|
@ -25,29 +25,28 @@ interface Workspace {
|
|||
interface Props {
|
||||
workspace: Workspace;
|
||||
pages: Page[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const formRef = ref<HTMLFormElement | null>(null);
|
||||
const selectedPageId = ref<string | null>(null);
|
||||
const form = useForm({ page_id: '' });
|
||||
|
||||
const handleSelectPage = (page: Page) => {
|
||||
selectedPageId.value = page.page_id;
|
||||
setTimeout(() => formRef.value?.submit(), 0);
|
||||
form.page_id = page.page_id;
|
||||
form.post(selectPage.url());
|
||||
};
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ?? '';
|
||||
const openExternal = (url: string | null) => {
|
||||
if (url) {
|
||||
window.open(url, '_blank', 'noopener');
|
||||
}
|
||||
};
|
||||
|
||||
const igUrl = (username: string): string => `https://www.instagram.com/${username}`;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopupLayout :title="$t('accounts.instagram_facebook.title')">
|
||||
<form ref="formRef" :action="selectPage.url()" method="POST" class="hidden">
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
<input type="hidden" name="page_id" :value="selectedPageId" />
|
||||
</form>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<img src="/images/accounts/instagram.png" alt="Instagram" class="h-10 w-10" />
|
||||
|
|
@ -57,11 +56,7 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Alert v-if="error" variant="destructive">
|
||||
<AlertDescription>{{ error }}</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div v-if="pages.length === 0 && !error" class="text-center py-12">
|
||||
<div v-if="pages.length === 0" class="py-12 text-center">
|
||||
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-full bg-muted">
|
||||
<IconBrandInstagram class="h-7 w-7 text-muted-foreground" />
|
||||
</div>
|
||||
|
|
@ -72,34 +67,36 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
</div>
|
||||
|
||||
<div v-else class="grid gap-3">
|
||||
<button
|
||||
<div
|
||||
v-for="page in pages"
|
||||
:key="page.ig_id"
|
||||
@click="handleSelectPage(page)"
|
||||
class="group relative overflow-hidden rounded-lg border bg-card p-4 text-left transition-all hover:border-primary hover:shadow-md focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||
class="flex items-center gap-4 rounded-lg border bg-card p-4"
|
||||
dusk="instagram-page"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<Avatar class="h-12 w-12 rounded-lg">
|
||||
<AvatarImage v-if="page.ig_picture" :src="page.ig_picture" class="object-cover" />
|
||||
<AvatarFallback class="rounded-lg bg-pink-100 dark:bg-pink-900">
|
||||
<IconBrandInstagram class="h-6 w-6 text-pink-600 dark:text-pink-400" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="font-semibold truncate group-hover:text-primary transition-colors">
|
||||
@{{ page.ig_username }}
|
||||
</h3>
|
||||
<p class="text-sm text-muted-foreground truncate">
|
||||
{{ page.page_name }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<div class="flex h-8 w-8 items-center justify-center rounded-full bg-primary text-primary-foreground">
|
||||
<IconCheck class="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<Avatar class="h-12 w-12 rounded-lg">
|
||||
<AvatarImage v-if="page.ig_picture" :src="page.ig_picture" class="object-cover" />
|
||||
<AvatarFallback class="rounded-lg bg-pink-100 dark:bg-pink-900">
|
||||
<IconBrandInstagram class="h-6 w-6 text-pink-600 dark:text-pink-400" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="min-w-0 flex-1">
|
||||
<h3 class="truncate font-semibold">@{{ page.ig_username }}</h3>
|
||||
<p class="truncate text-sm text-muted-foreground">{{ page.page_name }}</p>
|
||||
</div>
|
||||
</button>
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="openExternal(igUrl(page.ig_username))"
|
||||
>
|
||||
<IconExternalLink class="h-4 w-4" />
|
||||
{{ $t('accounts.instagram_facebook.view') }}
|
||||
</Button>
|
||||
<Button size="sm" dusk="choose-instagram-page" :disabled="form.processing" @click="handleSelectPage(page)">
|
||||
{{ $t('accounts.instagram_facebook.choose') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopupLayout>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import {
|
||||
IconBuilding,
|
||||
IconExternalLink,
|
||||
IconUser,
|
||||
IconUsers,
|
||||
} from '@tabler/icons-vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -31,33 +31,22 @@ const props = defineProps<{
|
|||
organizations: Organization[];
|
||||
}>();
|
||||
|
||||
const form = useForm({ type: 'person', organization_id: '' });
|
||||
|
||||
const isEmpty = computed(
|
||||
() => !props.person && props.organizations.length === 0,
|
||||
);
|
||||
|
||||
const formRef = ref<HTMLFormElement | null>(null);
|
||||
const selectedType = ref<'person' | 'organization'>('person');
|
||||
const selectedOrg = ref<Organization | null>(null);
|
||||
|
||||
const csrfToken =
|
||||
document
|
||||
.querySelector('meta[name="csrf-token"]')
|
||||
?.getAttribute('content') ?? '';
|
||||
|
||||
const submit = () => {
|
||||
setTimeout(() => formRef.value?.submit(), 0);
|
||||
};
|
||||
|
||||
const choosePerson = () => {
|
||||
selectedType.value = 'person';
|
||||
selectedOrg.value = null;
|
||||
submit();
|
||||
form.type = 'person';
|
||||
form.organization_id = '';
|
||||
form.post(selectLinkedIn.url());
|
||||
};
|
||||
|
||||
const chooseOrganization = (org: Organization) => {
|
||||
selectedType.value = 'organization';
|
||||
selectedOrg.value = org;
|
||||
submit();
|
||||
form.type = 'organization';
|
||||
form.organization_id = org.id;
|
||||
form.post(selectLinkedIn.url());
|
||||
};
|
||||
|
||||
const openExternal = (url: string | null) => {
|
||||
|
|
@ -75,12 +64,6 @@ const organizationUrl = (vanity: string | null): string | null =>
|
|||
|
||||
<template>
|
||||
<PopupLayout :title="$t('accounts.linkedin.select_title')">
|
||||
<form ref="formRef" :action="selectLinkedIn.url()" method="POST" class="hidden">
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
<input type="hidden" name="type" :value="selectedType" />
|
||||
<input type="hidden" name="organization_id" :value="selectedOrg?.id ?? ''" />
|
||||
</form>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<img src="/images/accounts/linkedin.png" alt="LinkedIn" class="h-10 w-10" />
|
||||
|
|
@ -140,7 +123,7 @@ const organizationUrl = (vanity: string | null): string | null =>
|
|||
<IconExternalLink class="h-4 w-4" />
|
||||
{{ $t('accounts.linkedin.view') }}
|
||||
</Button>
|
||||
<Button size="sm" dusk="choose-person" @click="choosePerson">
|
||||
<Button size="sm" dusk="choose-person" :disabled="form.processing" @click="choosePerson">
|
||||
{{ $t('accounts.linkedin.choose') }}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -164,7 +147,7 @@ const organizationUrl = (vanity: string | null): string | null =>
|
|||
<span
|
||||
class="inline-flex shrink-0 items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground"
|
||||
>
|
||||
<IconUsers class="h-3 w-3" />
|
||||
<IconBuilding class="h-3 w-3" />
|
||||
{{ $t('accounts.linkedin.organization_tag') }}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -182,7 +165,7 @@ const organizationUrl = (vanity: string | null): string | null =>
|
|||
<IconExternalLink class="h-4 w-4" />
|
||||
{{ $t('accounts.linkedin.view') }}
|
||||
</Button>
|
||||
<Button size="sm" dusk="choose-organization" @click="chooseOrganization(org)">
|
||||
<Button size="sm" dusk="choose-organization" :disabled="form.processing" @click="chooseOrganization(org)">
|
||||
{{ $t('accounts.linkedin.choose') }}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { IconInfoCircle } from '@tabler/icons-vue';
|
||||
import { trans } from 'laravel-vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
@ -10,56 +10,34 @@ import { Label } from '@/components/ui/label';
|
|||
import PopupLayout from '@/layouts/PopupLayout.vue';
|
||||
import { authorize as authorizeMastodon } from '@/routes/app/social/mastodon';
|
||||
|
||||
interface Props {
|
||||
errors?: Record<string, string>;
|
||||
}
|
||||
const form = useForm({ instance: 'https://mastodon.social' });
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const formRef = ref<HTMLFormElement | null>(null);
|
||||
const instance = ref('https://mastodon.social');
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
const submit = () => {
|
||||
isSubmitting.value = true;
|
||||
formRef.value?.submit();
|
||||
};
|
||||
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') ?? '';
|
||||
const onSubmit = () => form.post(authorizeMastodon.url());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopupLayout :title="$t('accounts.mastodon.title')">
|
||||
<div class="max-w-md mx-auto">
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<img src="/images/accounts/mastodon.png" alt="Mastodon" class="h-12 w-12" />
|
||||
<img src="/images/accounts/mastodon.png" alt="Mastodon" class="h-10 w-10" />
|
||||
<div>
|
||||
<h1 class="text-xl font-bold tracking-tight">{{ $t('accounts.mastodon.title') }}</h1>
|
||||
<p class="text-sm text-muted-foreground">{{ $t('accounts.mastodon.description') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form
|
||||
ref="formRef"
|
||||
:action="authorizeMastodon.url()"
|
||||
method="POST"
|
||||
@submit.prevent="submit"
|
||||
class="space-y-4"
|
||||
>
|
||||
<input type="hidden" name="_token" :value="csrfToken" />
|
||||
|
||||
<form @submit.prevent="onSubmit" class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="instance">{{ $t('accounts.mastodon.instance_url') }}</Label>
|
||||
<Input
|
||||
id="instance"
|
||||
name="instance"
|
||||
v-model="instance"
|
||||
v-model="form.instance"
|
||||
type="url"
|
||||
:placeholder="trans('accounts.mastodon.instance_placeholder')"
|
||||
:class="{ 'border-destructive': errors?.instance }"
|
||||
:class="{ 'border-destructive': form.errors.instance }"
|
||||
/>
|
||||
<p v-if="errors?.instance" class="text-sm text-destructive">
|
||||
{{ errors.instance }}
|
||||
<p v-if="form.errors.instance" class="text-sm text-destructive">
|
||||
{{ form.errors.instance }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -68,8 +46,8 @@ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribut
|
|||
<AlertDescription class="inline">{{ $t('accounts.mastodon.instance_hint') }}</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Button type="submit" :disabled="isSubmitting" class="w-full">
|
||||
{{ isSubmitting ? $t('accounts.mastodon.submitting') : $t('accounts.mastodon.submit') }}
|
||||
<Button type="submit" :disabled="form.processing" class="w-full">
|
||||
{{ form.processing ? $t('accounts.mastodon.submitting') : $t('accounts.mastodon.submit') }}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
54
resources/js/pages/accounts/PopupCallback.vue
Normal file
54
resources/js/pages/accounts/PopupCallback.vue
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<script setup lang="ts">
|
||||
import { IconCircleCheck, IconCircleX } from '@tabler/icons-vue';
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
import PopupLayout from '@/layouts/PopupLayout.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
platform?: string | null;
|
||||
}>();
|
||||
|
||||
const CLOSE_DELAY = 1500;
|
||||
|
||||
onMounted(() => {
|
||||
if (window.opener && !window.opener.closed) {
|
||||
try {
|
||||
window.opener.postMessage(
|
||||
{
|
||||
type: 'social-oauth-callback',
|
||||
success: props.success,
|
||||
message: props.message,
|
||||
platform: props.platform ?? null,
|
||||
},
|
||||
window.location.origin,
|
||||
);
|
||||
} catch {
|
||||
// Opener may be cross-origin or already gone; the timed close still applies.
|
||||
}
|
||||
}
|
||||
|
||||
window.setTimeout(() => window.close(), CLOSE_DELAY);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopupLayout :title="success ? $t('accounts.popup_callback.title_success') : $t('accounts.popup_callback.title_error')">
|
||||
<div class="flex flex-col items-center justify-center gap-3 py-16 text-center" dusk="popup-callback">
|
||||
<div
|
||||
class="flex h-14 w-14 items-center justify-center rounded-full"
|
||||
:class="
|
||||
success
|
||||
? 'bg-green-100 text-green-600 dark:bg-green-900 dark:text-green-400'
|
||||
: 'bg-red-100 text-red-600 dark:bg-red-900 dark:text-red-400'
|
||||
"
|
||||
>
|
||||
<IconCircleCheck v-if="success" class="h-7 w-7" />
|
||||
<IconCircleX v-else class="h-7 w-7" />
|
||||
</div>
|
||||
<p class="text-lg font-medium text-foreground">{{ message }}</p>
|
||||
<p class="text-sm text-muted-foreground">{{ $t('accounts.popup_callback.closing') }}</p>
|
||||
</div>
|
||||
</PopupLayout>
|
||||
</template>
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ $success ? __('accounts.popup_callback.title_success') : __('accounts.popup_callback.title_error') }}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
background: #f9fafb;
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
.icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.message {
|
||||
color: #374151;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
.submessage {
|
||||
color: #6b7280;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="icon">{{ $success ? '✓' : '✕' }}</div>
|
||||
<div class="message">{{ $message }}</div>
|
||||
<div class="submessage">{{ __('accounts.popup_callback.closing') }}</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const result = {
|
||||
success: {{ $success ? 'true' : 'false' }},
|
||||
message: @json($message),
|
||||
platform: @json($platform ?? null)
|
||||
};
|
||||
|
||||
// Try to notify the parent window
|
||||
if (window.opener && !window.opener.closed) {
|
||||
try {
|
||||
window.opener.postMessage({
|
||||
type: 'social-oauth-callback',
|
||||
...result
|
||||
}, window.location.origin);
|
||||
} catch (e) {
|
||||
// If postMessage fails, just reload the opener
|
||||
try {
|
||||
window.opener.location.reload();
|
||||
} catch (e2) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to close immediately and with delay
|
||||
function closeWindow() {
|
||||
try {
|
||||
window.open('', '_self', '');
|
||||
window.close();
|
||||
} catch (e) {}
|
||||
|
||||
// Fallback: try again
|
||||
try {
|
||||
window.close();
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Close after a short delay
|
||||
setTimeout(closeWindow, 2000);
|
||||
|
||||
// If still open after 2 seconds, show manual close message
|
||||
setTimeout(function() {
|
||||
if (!window.closed) {
|
||||
document.querySelector('.submessage').textContent = @json(__('accounts.popup_callback.close_now'));
|
||||
}
|
||||
}, 2000);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->user = User::factory()->create();
|
||||
|
|
@ -45,8 +46,8 @@
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -107,7 +108,7 @@
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::Bluesky)->count())->toBe(2);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
use App\Enums\UserWorkspace\Role;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\User as SocialiteUser;
|
||||
|
||||
|
|
@ -52,7 +53,7 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.discord.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -74,7 +75,7 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.discord.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::Discord)->count())->toBe(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\User as SocialiteUser;
|
||||
|
||||
|
|
@ -70,8 +71,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.facebook.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -119,9 +120,9 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.facebook.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', __('accounts.popup_callback.network_taken'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', __('accounts.popup_callback.network_taken')));
|
||||
|
||||
// The duplicate was blocked by the observer — only the pre-existing account remains.
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::Facebook)->count())->toBe(1);
|
||||
|
|
@ -189,8 +190,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.facebook.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'No Facebook Pages found. You need to be an admin of at least one page.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'No Facebook Pages found. You need to be an admin of at least one page.'));
|
||||
});
|
||||
|
||||
test('facebook callback fails with expired session', function () {
|
||||
|
|
@ -199,8 +200,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.facebook.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('user can connect multiple facebook accounts in self-hosted mode', function () {
|
||||
|
|
@ -239,7 +240,7 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.facebook.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::Facebook)->count())->toBe(2);
|
||||
});
|
||||
|
|
@ -259,8 +260,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.facebook.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Error connecting account. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Error connecting account. Please try again.'));
|
||||
});
|
||||
|
||||
test('facebook page selection creates account', function () {
|
||||
|
|
@ -293,7 +294,7 @@
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -311,8 +312,8 @@
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('facebook page selection fails with invalid page id', function () {
|
||||
|
|
@ -337,6 +338,6 @@
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Page not found.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Page not found.'));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\User as SocialiteUser;
|
||||
|
||||
|
|
@ -62,8 +63,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.instagram.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -78,8 +79,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.instagram.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('user can connect multiple instagram accounts in self-hosted mode', function () {
|
||||
|
|
@ -115,7 +116,7 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.instagram.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::Instagram)->count())->toBe(2);
|
||||
});
|
||||
|
|
@ -150,9 +151,9 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.instagram.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', __('accounts.popup_callback.network_taken'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', __('accounts.popup_callback.network_taken')));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::Instagram)->count())->toBe(1);
|
||||
});
|
||||
|
|
@ -172,8 +173,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.instagram.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Error connecting account. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Error connecting account. Please try again.'));
|
||||
});
|
||||
|
||||
test('instagram connect redirects to create workspace if none exists', function () {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->user = User::factory()->create();
|
||||
|
|
@ -42,8 +43,8 @@
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -86,9 +87,9 @@
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', __('accounts.popup_callback.network_taken'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', __('accounts.popup_callback.network_taken')));
|
||||
|
||||
expect($this->workspace->socialAccounts()->whereIn('platform', [Platform::Instagram->value, Platform::InstagramFacebook->value])->count())->toBe(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -134,8 +134,8 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.linkedin.connect'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertInertia(fn (Assert $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', false));
|
||||
});
|
||||
|
||||
test('linkedin callback stores the person and organizations then redirects to the selector', function () {
|
||||
|
|
@ -185,8 +185,8 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.linkedin.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('linkedin callback handles oauth errors gracefully', function () {
|
||||
|
|
@ -200,8 +200,8 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.linkedin.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Error connecting account. Please try again.');
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('message', 'Error connecting account. Please try again.'));
|
||||
});
|
||||
|
||||
test('select-identity screen renders the person and organizations', function () {
|
||||
|
|
@ -271,7 +271,7 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
'type' => 'person',
|
||||
]);
|
||||
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', false));
|
||||
$this->assertDatabaseMissing('social_accounts', ['platform_user_id' => 'person-123']);
|
||||
});
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
'organization_id' => 123456,
|
||||
]);
|
||||
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', false));
|
||||
$this->assertDatabaseMissing('social_accounts', ['platform_user_id' => 123456]);
|
||||
});
|
||||
|
||||
|
|
@ -304,8 +304,8 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.linkedin.select-identity'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertInertia(fn (Assert $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', false));
|
||||
});
|
||||
|
||||
test('selecting the person creates a linkedin account', function () {
|
||||
|
|
@ -324,8 +324,8 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (Assert $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -384,7 +384,7 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', true));
|
||||
|
||||
$account = SocialAccount::where('platform', Platform::LinkedInPage->value)
|
||||
->where('platform_user_id', 123456)
|
||||
|
|
@ -416,7 +416,7 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
'organization_id' => 999,
|
||||
]);
|
||||
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', false));
|
||||
$this->assertDatabaseMissing('social_accounts', ['platform_user_id' => 999]);
|
||||
});
|
||||
|
||||
|
|
@ -462,8 +462,8 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
|
||||
// A redirect-back would strand the popup; it must answer with the self-closing callback view.
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertInertia(fn (Assert $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', false));
|
||||
$this->assertDatabaseMissing('social_accounts', ['platform_user_id' => 'person-123']);
|
||||
});
|
||||
|
||||
|
|
@ -473,8 +473,8 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('selecting the person shows network_taken when a linkedin page already occupies the network', function () {
|
||||
|
|
@ -500,8 +500,8 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', __('accounts.popup_callback.network_taken'));
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('message', __('accounts.popup_callback.network_taken')));
|
||||
|
||||
$this->assertDatabaseMissing('social_accounts', [
|
||||
'platform' => Platform::LinkedIn->value,
|
||||
|
|
@ -535,7 +535,7 @@ function captureLinkedInConnectScopes(object $test): array
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (Assert $page) => $page->where('success', true));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::LinkedInPage)->count())->toBe(2);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->user = User::factory()->create();
|
||||
|
|
@ -92,8 +93,8 @@
|
|||
]));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -122,7 +123,7 @@
|
|||
]));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
|
||||
$this->assertDatabaseMissing('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -139,8 +140,8 @@
|
|||
]));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('user can connect multiple mastodon accounts in self-hosted mode', function () {
|
||||
|
|
@ -178,7 +179,7 @@
|
|||
]));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::Mastodon)->count())->toBe(2);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\User as SocialiteUser;
|
||||
|
||||
|
|
@ -62,8 +63,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.pinterest.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -108,8 +109,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.pinterest.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('user can connect multiple pinterest accounts in self-hosted mode', function () {
|
||||
|
|
@ -143,7 +144,7 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.pinterest.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::Pinterest)->count())->toBe(2);
|
||||
});
|
||||
|
|
@ -163,6 +164,6 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.pinterest.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Error connecting account. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Error connecting account. Please try again.'));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
|
||||
beforeEach(function () {
|
||||
$this->user = User::factory()->create();
|
||||
|
|
@ -59,8 +60,8 @@
|
|||
]));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -83,8 +84,8 @@
|
|||
]));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Invalid state. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Invalid state. Please try again.'));
|
||||
|
||||
$this->assertDatabaseMissing('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -101,8 +102,8 @@
|
|||
]));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('user can connect multiple threads accounts in self-hosted mode', function () {
|
||||
|
|
@ -142,7 +143,7 @@
|
|||
]));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::Threads)->count())->toBe(2);
|
||||
});
|
||||
|
|
@ -168,8 +169,8 @@
|
|||
]));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Error connecting account. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Error connecting account. Please try again.'));
|
||||
});
|
||||
|
||||
test('threads callback shows network_taken when the network is already connected', function () {
|
||||
|
|
@ -210,9 +211,9 @@
|
|||
]));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', __('accounts.popup_callback.network_taken'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', __('accounts.popup_callback.network_taken')));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::Threads)->count())->toBe(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\User as SocialiteUser;
|
||||
|
||||
|
|
@ -64,8 +65,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.tiktok.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -82,8 +83,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.tiktok.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('user can connect multiple tiktok accounts in self-hosted mode', function () {
|
||||
|
|
@ -119,7 +120,7 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.tiktok.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::TikTok)->count())->toBe(2);
|
||||
});
|
||||
|
|
@ -140,6 +141,6 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.tiktok.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Error connecting account. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Error connecting account. Please try again.'));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\User as SocialiteUser;
|
||||
|
||||
|
|
@ -62,8 +63,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.x.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -80,8 +81,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.x.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('user can connect multiple x accounts in self-hosted mode', function () {
|
||||
|
|
@ -115,7 +116,7 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.x.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::X)->count())->toBe(2);
|
||||
});
|
||||
|
|
@ -135,6 +136,6 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.x.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Error connecting account. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Error connecting account. Please try again.'));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Inertia\Testing\AssertableInertia;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\User as SocialiteUser;
|
||||
|
||||
|
|
@ -81,8 +82,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.youtube.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -168,8 +169,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.youtube.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'No YouTube channels found. Please create a channel first.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'No YouTube channels found. Please create a channel first.'));
|
||||
});
|
||||
|
||||
test('youtube callback fails with expired session', function () {
|
||||
|
|
@ -178,8 +179,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.youtube.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('user can connect multiple youtube accounts in self-hosted mode', function () {
|
||||
|
|
@ -225,7 +226,7 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.youtube.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::YouTube)->count())->toBe(2);
|
||||
});
|
||||
|
|
@ -245,8 +246,8 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.youtube.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Error connecting account. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Error connecting account. Please try again.'));
|
||||
});
|
||||
|
||||
test('youtube channel selection creates account', function () {
|
||||
|
|
@ -282,7 +283,7 @@
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', true);
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', true));
|
||||
|
||||
$this->assertDatabaseHas('social_accounts', [
|
||||
'workspace_id' => $this->workspace->id,
|
||||
|
|
@ -300,8 +301,8 @@
|
|||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', 'Session expired. Please try again.');
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', 'Session expired. Please try again.'));
|
||||
});
|
||||
|
||||
test('youtube callback shows network_taken when the network is already connected', function () {
|
||||
|
|
@ -347,9 +348,9 @@
|
|||
$response = $this->actingAs($this->user)->get(route('app.social.youtube.callback'));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewIs('auth.social-callback');
|
||||
$response->assertViewHas('success', false);
|
||||
$response->assertViewHas('message', __('accounts.popup_callback.network_taken'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->component('accounts/PopupCallback'));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('success', false));
|
||||
$response->assertInertia(fn (AssertableInertia $page) => $page->where('message', __('accounts.popup_callback.network_taken')));
|
||||
|
||||
expect($this->workspace->socialAccounts()->where('platform', Platform::YouTube)->count())->toBe(1);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue