Skip to content
This repository was archived by the owner on Sep 5, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 63 additions & 10 deletions resources/js/vue/pages/Profile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,19 @@ 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';
import IconGoogle from '~icons/simple-icons/google';

const title = 'Profile';

type SocialiteProvider = {
name: string;
label: string;
};

const props = defineProps<{
user: User & {
social_accounts?: Array<{
Expand All @@ -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(' ')
Expand Down Expand Up @@ -126,11 +130,52 @@ const getProviderIcon = (providerName: string) => {
return providerIcons[providerName.toLowerCase()];
};

const enabledProviders = computed<SocialiteProvider[]>(() => {
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<SocialiteProvider[]>(() => {
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
);
});
</script>
Expand Down Expand Up @@ -232,9 +277,10 @@ const hasSocialiteProviders = computed(() => {
<CardContent>
<div class="space-y-4">
<div
v-for="provider in available_providers"
v-for="provider in socialiteProviders"
:key="provider.name"
class="flex items-center justify-between rounded-lg border p-4"
:data-testid="`socialite-account-${provider.name}`"
>
<div class="flex items-center gap-4">
<!-- Provider Icon -->
Expand Down Expand Up @@ -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
"
Expand All @@ -304,7 +351,7 @@ const hasSocialiteProviders = computed(() => {

<!-- Connect Button (for not connected providers) -->
<Button
v-else
v-else-if="isProviderEnabled(provider.name)"
as="a"
:href="
route(
Expand All @@ -314,6 +361,7 @@ const hasSocialiteProviders = computed(() => {
"
variant="default"
size="sm"
:data-testid="`connect-socialite-${provider.name}`"
>
{{ $t('Connect') }}
</Button>
Expand Down Expand Up @@ -342,11 +390,16 @@ const hasSocialiteProviders = computed(() => {
<DialogFooter>
<Button
variant="outline"
data-testid="cancel-socialite-disconnect"
@click="isDisconnectDialogOpen = false"
>
{{ $t('Cancel') }}
</Button>
<Button variant="destructive" @click="confirmDisconnect">
<Button
variant="destructive"
data-testid="confirm-socialite-disconnect"
@click="confirmDisconnect"
>
{{ $t('Disconnect') }}
</Button>
</DialogFooter>
Expand Down
60 changes: 60 additions & 0 deletions tests/e2e/tests/profile/socialite.settings.spec.ts
Original file line number Diff line number Diff line change
@@ -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'],
);
}
});
});