diff --git a/resources/js/vue/pages/Profile.vue b/resources/js/vue/pages/Profile.vue index 28efad6..458cf0a 100644 --- a/resources/js/vue/pages/Profile.vue +++ b/resources/js/vue/pages/Profile.vue @@ -22,7 +22,7 @@ import { import Separator from '@/components/ui/separator/Separator.vue'; import SettingsLayout from '@/layouts/SettingsLayout.vue'; import type { User } from '@/types'; -import { Link, router } from '@inertiajs/vue3'; +import { Link, router, usePage } from '@inertiajs/vue3'; import { Loader2 } from '@lucide/vue'; import { computed, ref } from 'vue'; import IconGithub from '~icons/simple-icons/github'; @@ -30,6 +30,11 @@ import IconGoogle from '~icons/simple-icons/google'; const title = 'Profile'; +type SocialiteProvider = { + name: string; + label: string; +}; + const props = defineProps<{ user: User & { social_accounts?: Array<{ @@ -38,12 +43,11 @@ const props = defineProps<{ provider_avatar_url?: string; }>; }; - available_providers?: Array<{ - name: string; - label: string; - }>; + available_providers?: SocialiteProvider[]; }>(); +const page = usePage(); + const getInitials = (name: string) => { return name .split(' ') @@ -126,11 +130,52 @@ const getProviderIcon = (providerName: string) => { return providerIcons[providerName.toLowerCase()]; }; +const enabledProviders = computed(() => { + const auth = page.props.auth as { + socialite_providers?: SocialiteProvider[]; + }; + + return auth.socialite_providers ?? []; +}); + +const enabledProviderNames = computed( + () => new Set(enabledProviders.value.map((provider) => provider.name)), +); + +const isProviderEnabled = (providerName: string): boolean => { + return enabledProviderNames.value.has(providerName); +}; + +const socialiteProviders = computed(() => { + const configuredProviders = new Map( + (props.available_providers ?? []).map((provider) => [ + provider.name, + provider, + ]), + ); + const providers = new Map( + enabledProviders.value.map((provider) => [provider.name, provider]), + ); + + for (const account of props.user.social_accounts ?? []) { + if (!providers.has(account.provider)) { + providers.set( + account.provider, + configuredProviders.get(account.provider) ?? { + name: account.provider, + label: account.provider, + }, + ); + } + } + + return [...providers.values()]; +}); + const hasSocialiteProviders = computed(() => { return ( route().has('auth.socialite.redirect') && - props.available_providers && - props.available_providers.length > 0 + socialiteProviders.value.length > 0 ); }); @@ -232,9 +277,10 @@ const hasSocialiteProviders = computed(() => {
@@ -286,6 +332,7 @@ const hasSocialiteProviders = computed(() => { variant="destructive" size="sm" @click="initiateDisconnect(provider.name)" + :data-testid="`disconnect-socialite-${provider.name}`" :disabled=" isDisconnecting === provider.name " @@ -304,7 +351,7 @@ const hasSocialiteProviders = computed(() => { @@ -342,11 +390,16 @@ const hasSocialiteProviders = computed(() => { - diff --git a/tests/e2e/tests/profile/socialite.settings.spec.ts b/tests/e2e/tests/profile/socialite.settings.spec.ts new file mode 100644 index 0000000..e456456 --- /dev/null +++ b/tests/e2e/tests/profile/socialite.settings.spec.ts @@ -0,0 +1,60 @@ +import { expect, test } from '@e2e/fixtures'; + +test.describe('Profile Socialite Settings', () => { + test.describe.configure({ mode: 'serial' }); + + test('keeps a disabled connected provider available for disconnect', async ({ + page, + laravel, + credentials, + loginAs, + }) => { + const providerId = `e2e-disabled-google-${Date.now()}`; + const [socialiteProviderSetting] = await laravel.select( + 'SELECT payload FROM settings WHERE name = :name', + { name: 'enabled_socialite_providers' }, + ); + const originalPayload = socialiteProviderSetting?.payload; + + if (typeof originalPayload !== 'string') { + throw new Error('Socialite provider setting was not found.'); + } + + try { + await laravel.query( + 'UPDATE settings SET payload = ? WHERE name = ?', + ['[]', 'enabled_socialite_providers'], + ); + await laravel.query( + 'INSERT INTO social_accounts (user_id, provider, provider_id, last_login_at, created_at, updated_at) SELECT id, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP FROM users WHERE email = ?', + ['google', providerId, credentials.user.email], + ); + + await loginAs(credentials.user); + await page.goto('/settings/profile'); + + await expect( + page.getByTestId('socialite-account-google'), + ).toBeVisible(); + await expect( + page.getByTestId('connect-socialite-google'), + ).not.toBeVisible(); + + await page.getByTestId('disconnect-socialite-google').click(); + await page.getByTestId('confirm-socialite-disconnect').click(); + + await expect( + page.getByTestId('socialite-account-google'), + ).not.toBeVisible(); + } finally { + await laravel.query( + 'DELETE FROM social_accounts WHERE provider = ? AND provider_id = ?', + ['google', providerId], + ); + await laravel.query( + 'UPDATE settings SET payload = ? WHERE name = ?', + [originalPayload, 'enabled_socialite_providers'], + ); + } + }); +});