Skip to content
Merged
35 changes: 35 additions & 0 deletions .changeset/unlucky-readers-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
"@clerk/vue": minor
---

Add support for `<OrganizationProfile>` custom pages and links through `<OrganizationSwitcher>`

Example:

```vue
<script setup lang="ts">
import { OrganizationSwitcher } from '@clerk/vue'
import Icon from './Icon.vue'
</script>

<template>
<header>
<OrganizationSwitcher>
<OrganizationSwitcher.OrganizationProfilePage label="Custom Page" url="custom">
<template #labelIcon>
<Icon />
</template>
<div>
<h1>Custom Organization Profile Page</h1>
<p>This is the custom organization profile page</p>
</div>
</OrganizationSwitcher.OrganizationProfilePage>
<OrganizationSwitcher.OrganizationProfileLink label="Homepage" url="/">
<template #labelIcon>
<Icon />
</template>
</OrganizationSwitcher.OrganizationProfileLink>
</OrganizationSwitcher>
</header>
</template>
```
3 changes: 1 addition & 2 deletions integration/templates/vue-vite/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { SignedIn, SignedOut, OrganizationSwitcher, ClerkLoaded, ClerkLoading } from '@clerk/vue';
import { SignedIn, SignedOut, ClerkLoaded, ClerkLoading } from '@clerk/vue';
import CustomUserButton from './components/CustomUserButton.vue';
</script>

Expand All @@ -11,7 +11,6 @@ import CustomUserButton from './components/CustomUserButton.vue';
</div>
<SignedIn>
<CustomUserButton />
<OrganizationSwitcher />
</SignedIn>
<SignedOut>
<RouterLink to="/sign-in">Sign in</RouterLink>
Expand Down
5 changes: 4 additions & 1 deletion integration/templates/vue-vite/src/views/Home.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<script setup lang="ts">
import { useAuth } from '@clerk/vue';
import { SignedIn, OrganizationSwitcher, useAuth } from '@clerk/vue';

const { isSignedIn } = useAuth();
</script>

<template>
<SignedIn>
<OrganizationSwitcher />
</SignedIn>
<div>
<ul>
<li v-if="isSignedIn"><RouterLink to="/profile">Profile</RouterLink></li>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
<script setup lang="ts">
import { OrganizationProfile } from '@clerk/vue';
import { OrganizationProfile, OrganizationSwitcher } from '@clerk/vue';
</script>

<template>
<OrganizationSwitcher>
<OrganizationSwitcher.OrganizationProfilePage
label="Terms"
url="terms"
>
<template #labelIcon>
<div>Icon</div>
</template>
<div>
<h1>Custom Terms Page</h1>
<p>This is the custom terms page</p>
</div>
</OrganizationSwitcher.OrganizationProfilePage>
<OrganizationSwitcher.OrganizationProfileLink
label="Homepage"
url="/"
>
<template #labelIcon>
<div>Icon</div>
</template>
</OrganizationSwitcher.OrganizationProfileLink>
</OrganizationSwitcher>
<OrganizationProfile>
<OrganizationProfile.Page
label="Terms"
Expand Down
34 changes: 33 additions & 1 deletion integration/tests/vue/components.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
await u.page.waitForAppUrl('/');
});

test('render organization profile with custom pages and links', async ({ page, context }) => {
test('render organization profile with custom pages and links in a dedicated page', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.page.goToRelative('/sign-in');
await u.po.signIn.waitForMounted();
Expand Down Expand Up @@ -206,6 +206,38 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
await u.page.waitForAppUrl('/');
});

test('render organization profile with custom pages and links inside an organization switcher', async ({
page,
context,
}) => {
const u = createTestUtils({ app, page, context });
await u.page.goToRelative('/sign-in');
await u.po.signIn.waitForMounted();
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
await u.po.expect.toBeSignedIn();

await u.page.goToRelative('/custom-pages/organization-profile');
await u.po.organizationSwitcher.waitForMounted();
await u.po.organizationSwitcher.waitForAnOrganizationToSelected();

// Open organization profile inside organization switcher
await u.po.organizationSwitcher.toggleTrigger();
await u.page.waitForSelector('.cl-organizationSwitcherPopoverCard', { state: 'visible' });
await u.page.locator('.cl-button__manageOrganization').click();

// Check if custom pages and links are visible
await expect(u.page.getByRole('button', { name: /Terms/i })).toBeVisible();
await expect(u.page.getByRole('button', { name: /Homepage/i })).toBeVisible();

// Navigate to custom page
await u.page.getByRole('button', { name: /Terms/i }).click();
await expect(u.page.getByRole('heading', { name: 'Custom Terms Page' })).toBeVisible();

// Click custom link and check navigation
await u.page.getByRole('button', { name: /Homepage/i }).click();
await u.page.waitForAppUrl('/');
});

test('redirects to sign-in when unauthenticated', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.page.goToRelative('/profile');
Expand Down
3 changes: 1 addition & 2 deletions packages/vue/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ export { default as SignIn } from './ui-components/SignIn.vue';
export { default as SignUp } from './ui-components/SignUp.vue';
export { default as GoogleOneTap } from './ui-components/GoogleOneTap.vue';
export { default as Waitlist } from './ui-components/Waitlist.vue';
export { default as OrganizationSwitcher } from './ui-components/OrganizationSwitcher.vue';
export { default as CreateOrganization } from './ui-components/CreateOrganization.vue';
export { default as OrganizationList } from './ui-components/OrganizationList.vue';
export { UserProfile } from './ui-components/UserProfile';
export { OrganizationProfile } from './ui-components/OrganizationProfile';
export { OrganizationSwitcher } from './ui-components/OrganizationSwitcher';
export { UserButton } from './ui-components/UserButton';

export {
ClerkLoaded,
ClerkLoading,
Expand Down
17 changes: 0 additions & 17 deletions packages/vue/src/components/ui-components/OrganizationSwitcher.vue

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script setup lang="ts">
import { useClerk } from '../../../composables';
import type { OrganizationSwitcherProps, OrganizationProfileProps } from '@clerk/types';
import { ClerkHostRenderer, CustomPortalsRenderer } from '../../ClerkHostRenderer';
import { useOrganizationProfileCustomPages } from '../../../utils/useCustomPages';
import { computed, provide } from 'vue';
import { OrganizationProfileInjectionKey } from '../../../keys';

const clerk = useClerk();

type Props = Omit<OrganizationSwitcherProps, 'organizationProfileProps' | '__experimental_asStandalone'> & {
organizationProfileProps?: Pick<OrganizationProfileProps, 'appearance'>;
};
const props = defineProps<Props>();

const { customPages, customPagesPortals, addCustomPage } = useOrganizationProfileCustomPages();

const finalProps = computed<Props>(() => ({
...props,
organizationProfileProps: {
...(props.organizationProfileProps || {}),
customPages: customPages.value,
},
}));

provide(OrganizationProfileInjectionKey, {
addCustomPage,
});
</script>

<template>
<ClerkHostRenderer
:mount="clerk?.mountOrganizationSwitcher"
:unmount="clerk?.unmountOrganizationSwitcher"
:update-props="(clerk as any)?.__unstable__updateProps"
:props="finalProps"
/>
<CustomPortalsRenderer :custom-pages-portals="customPagesPortals" />
<slot />
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { OrganizationProfileLink, OrganizationProfilePage } from '../OrganizationProfile';
import _OrganizationSwitcher from './OrganizationSwitcher.vue';

export const OrganizationSwitcher = Object.assign(_OrganizationSwitcher, {
OrganizationProfilePage,
OrganizationProfileLink,
});