Skip to content

Commit 98436f0

Browse files
authored
feat(vue): <OrganizationProfile> custom pages and links through <OrganizationSwitcher> (#5129)
1 parent 128fd89 commit 98436f0

File tree

9 files changed

+144
-24
lines changed

9 files changed

+144
-24
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
"@clerk/vue": minor
3+
---
4+
5+
Add support for `<OrganizationProfile>` custom pages and links through `<OrganizationSwitcher>`
6+
7+
Example:
8+
9+
```vue
10+
<script setup lang="ts">
11+
import { OrganizationSwitcher } from '@clerk/vue'
12+
import Icon from './Icon.vue'
13+
</script>
14+
15+
<template>
16+
<header>
17+
<OrganizationSwitcher>
18+
<OrganizationSwitcher.OrganizationProfilePage label="Custom Page" url="custom">
19+
<template #labelIcon>
20+
<Icon />
21+
</template>
22+
<div>
23+
<h1>Custom Organization Profile Page</h1>
24+
<p>This is the custom organization profile page</p>
25+
</div>
26+
</OrganizationSwitcher.OrganizationProfilePage>
27+
<OrganizationSwitcher.OrganizationProfileLink label="Homepage" url="/">
28+
<template #labelIcon>
29+
<Icon />
30+
</template>
31+
</OrganizationSwitcher.OrganizationProfileLink>
32+
</OrganizationSwitcher>
33+
</header>
34+
</template>
35+
```

integration/templates/vue-vite/src/App.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { SignedIn, SignedOut, OrganizationSwitcher, ClerkLoaded, ClerkLoading } from '@clerk/vue';
2+
import { SignedIn, SignedOut, ClerkLoaded, ClerkLoading } from '@clerk/vue';
33
import CustomUserButton from './components/CustomUserButton.vue';
44
</script>
55

@@ -11,7 +11,6 @@ import CustomUserButton from './components/CustomUserButton.vue';
1111
</div>
1212
<SignedIn>
1313
<CustomUserButton />
14-
<OrganizationSwitcher />
1514
</SignedIn>
1615
<SignedOut>
1716
<RouterLink to="/sign-in">Sign in</RouterLink>

integration/templates/vue-vite/src/views/Home.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
<script setup lang="ts">
2-
import { useAuth } from '@clerk/vue';
2+
import { SignedIn, OrganizationSwitcher, useAuth } from '@clerk/vue';
33
44
const { isSignedIn } = useAuth();
55
</script>
66

77
<template>
8+
<SignedIn>
9+
<OrganizationSwitcher />
10+
</SignedIn>
811
<div>
912
<ul>
1013
<li v-if="isSignedIn"><RouterLink to="/profile">Profile</RouterLink></li>

integration/templates/vue-vite/src/views/custom-pages/OrganizationProfile.vue

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
11
<script setup lang="ts">
2-
import { OrganizationProfile } from '@clerk/vue';
2+
import { OrganizationProfile, OrganizationSwitcher } from '@clerk/vue';
33
</script>
44

55
<template>
6+
<OrganizationSwitcher>
7+
<OrganizationSwitcher.OrganizationProfilePage
8+
label="Terms"
9+
url="terms"
10+
>
11+
<template #labelIcon>
12+
<div>Icon</div>
13+
</template>
14+
<div>
15+
<h1>Custom Terms Page</h1>
16+
<p>This is the custom terms page</p>
17+
</div>
18+
</OrganizationSwitcher.OrganizationProfilePage>
19+
<OrganizationSwitcher.OrganizationProfileLink
20+
label="Homepage"
21+
url="/"
22+
>
23+
<template #labelIcon>
24+
<div>Icon</div>
25+
</template>
26+
</OrganizationSwitcher.OrganizationProfileLink>
27+
</OrganizationSwitcher>
628
<OrganizationProfile>
729
<OrganizationProfile.Page
830
label="Terms"

integration/tests/vue/components.test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withCustomRoles] })('basic te
178178
await u.page.waitForAppUrl('/');
179179
});
180180

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

209+
test('render organization profile with custom pages and links inside an organization switcher', async ({
210+
page,
211+
context,
212+
}) => {
213+
const u = createTestUtils({ app, page, context });
214+
await u.page.goToRelative('/sign-in');
215+
await u.po.signIn.waitForMounted();
216+
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
217+
await u.po.expect.toBeSignedIn();
218+
219+
await u.page.goToRelative('/custom-pages/organization-profile');
220+
await u.po.organizationSwitcher.waitForMounted();
221+
await u.po.organizationSwitcher.waitForAnOrganizationToSelected();
222+
223+
// Open organization profile inside organization switcher
224+
await u.po.organizationSwitcher.toggleTrigger();
225+
await u.page.waitForSelector('.cl-organizationSwitcherPopoverCard', { state: 'visible' });
226+
await u.page.locator('.cl-button__manageOrganization').click();
227+
228+
// Check if custom pages and links are visible
229+
await expect(u.page.getByRole('button', { name: /Terms/i })).toBeVisible();
230+
await expect(u.page.getByRole('button', { name: /Homepage/i })).toBeVisible();
231+
232+
// Navigate to custom page
233+
await u.page.getByRole('button', { name: /Terms/i }).click();
234+
await expect(u.page.getByRole('heading', { name: 'Custom Terms Page' })).toBeVisible();
235+
236+
// Click custom link and check navigation
237+
await u.page.getByRole('button', { name: /Homepage/i }).click();
238+
await u.page.waitForAppUrl('/');
239+
});
240+
209241
test('redirects to sign-in when unauthenticated', async ({ page, context }) => {
210242
const u = createTestUtils({ app, page, context });
211243
await u.page.goToRelative('/profile');

packages/vue/src/components/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ export { default as SignIn } from './ui-components/SignIn.vue';
22
export { default as SignUp } from './ui-components/SignUp.vue';
33
export { default as GoogleOneTap } from './ui-components/GoogleOneTap.vue';
44
export { default as Waitlist } from './ui-components/Waitlist.vue';
5-
export { default as OrganizationSwitcher } from './ui-components/OrganizationSwitcher.vue';
65
export { default as CreateOrganization } from './ui-components/CreateOrganization.vue';
76
export { default as OrganizationList } from './ui-components/OrganizationList.vue';
87
export { UserProfile } from './ui-components/UserProfile';
98
export { OrganizationProfile } from './ui-components/OrganizationProfile';
9+
export { OrganizationSwitcher } from './ui-components/OrganizationSwitcher';
1010
export { UserButton } from './ui-components/UserButton';
11-
1211
export {
1312
ClerkLoaded,
1413
ClerkLoading,

packages/vue/src/components/ui-components/OrganizationSwitcher.vue

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script setup lang="ts">
2+
import { useClerk } from '../../../composables';
3+
import type { OrganizationSwitcherProps, OrganizationProfileProps } from '@clerk/types';
4+
import { ClerkHostRenderer, CustomPortalsRenderer } from '../../ClerkHostRenderer';
5+
import { useOrganizationProfileCustomPages } from '../../../utils/useCustomPages';
6+
import { computed, provide } from 'vue';
7+
import { OrganizationProfileInjectionKey } from '../../../keys';
8+
9+
const clerk = useClerk();
10+
11+
type Props = Omit<OrganizationSwitcherProps, 'organizationProfileProps' | '__experimental_asStandalone'> & {
12+
organizationProfileProps?: Pick<OrganizationProfileProps, 'appearance'>;
13+
};
14+
const props = defineProps<Props>();
15+
16+
const { customPages, customPagesPortals, addCustomPage } = useOrganizationProfileCustomPages();
17+
18+
const finalProps = computed<Props>(() => ({
19+
...props,
20+
organizationProfileProps: {
21+
...(props.organizationProfileProps || {}),
22+
customPages: customPages.value,
23+
},
24+
}));
25+
26+
provide(OrganizationProfileInjectionKey, {
27+
addCustomPage,
28+
});
29+
</script>
30+
31+
<template>
32+
<ClerkHostRenderer
33+
:mount="clerk?.mountOrganizationSwitcher"
34+
:unmount="clerk?.unmountOrganizationSwitcher"
35+
:update-props="(clerk as any)?.__unstable__updateProps"
36+
:props="finalProps"
37+
/>
38+
<CustomPortalsRenderer :custom-pages-portals="customPagesPortals" />
39+
<slot />
40+
</template>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { OrganizationProfileLink, OrganizationProfilePage } from '../OrganizationProfile';
2+
import _OrganizationSwitcher from './OrganizationSwitcher.vue';
3+
4+
export const OrganizationSwitcher = Object.assign(_OrganizationSwitcher, {
5+
OrganizationProfilePage,
6+
OrganizationProfileLink,
7+
});

0 commit comments

Comments
 (0)