Skip to content

Commit

Permalink
Merge branch 'main' into alexcarpenter/input-primitive-placeholder-color
Browse files Browse the repository at this point in the history
  • Loading branch information
alexcarpenter authored Feb 19, 2025
2 parents ad97f3e + a310a23 commit 14552ba
Show file tree
Hide file tree
Showing 134 changed files with 2,421 additions and 1,163 deletions.
5 changes: 0 additions & 5 deletions .changeset/angry-ravens-grin.md

This file was deleted.

15 changes: 0 additions & 15 deletions .changeset/dry-keys-look.md

This file was deleted.

11 changes: 11 additions & 0 deletions .changeset/nasty-mangos-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@clerk/elements': minor
'@clerk/shared': minor
'@clerk/astro': minor
'@clerk/clerk-react': minor
'@clerk/types': minor
'@clerk/clerk-expo': minor
'@clerk/vue': minor
---

Surface new `pending` session as a signed-in state
5 changes: 5 additions & 0 deletions .changeset/new-fishes-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': patch
---

Bug fix: Broadcast a sign out event to all opened tabs when `Clerk.signOut()` or `User.delete()` is called.
11 changes: 11 additions & 0 deletions .changeset/olive-elephants-share.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@clerk/agent-toolkit': patch
'@clerk/react-router': patch
'@clerk/astro': patch
'@clerk/nuxt': patch
'@clerk/vue': patch
---

The [`exports` map](https://nodejs.org/api/packages.html#conditional-exports) inside `package.json` has been slightly adjusted to allow for [`require(esm)`](https://joyeecheung.github.io/blog/2024/03/18/require-esm-in-node-js/) to work correctly. The `"import"` conditions have been changed to `"default"`.

You shouldn't see any change in behavior/functionality on your end.
6 changes: 6 additions & 0 deletions .changeset/plenty-peas-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': patch
'@clerk/types': patch
---

Initialize `tasks` on `Session` resource
20 changes: 20 additions & 0 deletions .changeset/proud-cycles-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@clerk/clerk-js': minor
---

- Initialize new `pending` session status as an signed-in state
- Deprecate `Clerk.client.activeSessions` in favor of `Clerk.client.signedInSessions`
- Introduce `Clerk.isSignedIn` property as an explicit signed-in state check, instead of `!!Clerk.session` or `!!Clerk.user`:

```ts
- if (Clerk.user) {
+ if (Clerk.isSignedIn) {
// Mount user button component
document.getElementById('signed-in').innerHTML = `
<div id="user-button"></div>
`

const userbuttonDiv = document.getElementById('user-button')

clerk.mountUserButton(userbuttonDiv)
}
5 changes: 0 additions & 5 deletions .changeset/quiet-ladybugs-reply.md

This file was deleted.

File renamed without changes.
2 changes: 2 additions & 0 deletions .changeset/tall-hounds-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
12 changes: 0 additions & 12 deletions .changeset/unlucky-spiders-kick.md

This file was deleted.

2 changes: 1 addition & 1 deletion .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"*.{mjs,js,jsx,ts,tsx}": ["pnpm eslint --fix", "pnpm prettier --write"],
"*.{mjs,js,jsx,ts,tsx,vue}": ["pnpm eslint --fix", "pnpm prettier --write"],
"*.{json,md,mdx}": ["pnpm prettier --write"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ export default function Page() {
<div>
<SignIn
routing={'path'}
path={'/sign-in'}
path={'/sign-in-or-up'}
signUpUrl={'/sign-up'}
fallback={<>Loading sign in</>}
unsafeMetadata={{ position: 'goalie' }}
withSignUp
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default function Page() {
path={'/sign-up'}
signInUrl={'/sign-in'}
fallback={<>Loading sign up</>}
unsafeMetadata={{ position: 'goalie' }}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { UserButton } from '@clerk/clerk-react';
import { PageContextProvider } from '../PageContext.tsx';
import React from 'react';

export default function Page() {
const [open, setIsOpen] = React.useState(false);
const [theme, setTheme] = React.useState('light');
const [notifications, setNotifications] = React.useState(false);
const [language, setLanguage] = React.useState('en');

return (
<PageContextProvider>
<UserButton
fallback={<>Loading user button</>}
userProfileProps={{ appearance: { elements: { modalBackdrop: { zIndex: '100' } } } }}
>
<UserButton.MenuItems>
<UserButton.Action
label={`Chat is ${open ? 'ON' : 'OFF'}`}
labelIcon={<span>🌐</span>}
onClick={() => setIsOpen(!open)}
/>
<UserButton.Action
label={`Theme: ${theme === 'light' ? '☀️ Light' : '🌙 Dark'}`}
labelIcon={<span>🌐</span>}
onClick={() => setTheme(t => (t === 'light' ? 'dark' : 'light'))}
/>
<UserButton.Action
label={`Notifications ${notifications ? '🔔 ON' : '🔕 OFF'}`}
labelIcon={<span>🌐</span>}
onClick={() => setNotifications(n => !n)}
/>
<UserButton.Action
label={`Language: ${language.toUpperCase()}`}
labelIcon={<span>🌍</span>}
onClick={() => setLanguage(l => (l === 'en' ? 'es' : 'en'))}
/>
<UserButton.Action label={'manageAccount'} />
<UserButton.Action label={'signOut'} />
<UserButton.Link
href={'http://clerk.com'}
label={'Visit Clerk'}
labelIcon={<span>🌐</span>}
/>

<UserButton.Link
href={'/user'}
label={'Visit User page'}
labelIcon={<span>🌐</span>}
/>

<UserButton.Action
label={'Custom Alert'}
labelIcon={<span>🔔</span>}
onClick={() => alert('custom-alert')}
/>
</UserButton.MenuItems>
<UserButton.UserProfilePage
label='Notifications Page'
url='notifications'
labelIcon={<span>🔔</span>}
>
<h1 data-page='notifications-page'>Notifications page</h1>
</UserButton.UserProfilePage>
</UserButton>
</PageContextProvider>
);
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,7 @@
import { UserButton } from '@clerk/clerk-react';
import { useContext } from 'react';
import { PageContext, PageContextProvider } from '../PageContext.tsx';
import { PageContextProvider } from '../PageContext.tsx';
import React from 'react';

function Page1() {
const { counter, setCounter } = useContext(PageContext);

return (
<>
<h1 data-page={1}>Page 1</h1>
<p data-page={1}>Counter: {counter}</p>
<button
data-page={1}
onClick={() => setCounter(a => a + 1)}
>
Update
</button>
</>
);
}

export default function Page() {
const [open, setIsOpen] = React.useState(false);
const [theme, setTheme] = React.useState('light');
Expand Down
5 changes: 5 additions & 0 deletions integration/templates/react-vite/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import UserProfile from './user';
import UserProfileCustom from './custom-user-profile';
import UserButtonCustom from './custom-user-button';
import UserButtonCustomDynamicLabels from './custom-user-button/with-dynamic-labels.tsx';
import UserButtonCustomDynamicLabelsAndCustomPages from './custom-user-button/with-dynamic-label-and-custom-pages.tsx';
import UserButtonCustomTrigger from './custom-user-button-trigger';
import UserButton from './user-button';
import Waitlist from './waitlist';
Expand Down Expand Up @@ -85,6 +86,10 @@ const router = createBrowserRouter([
path: '/custom-user-button-dynamic-labels',
element: <UserButtonCustomDynamicLabels />,
},
{
path: '/custom-user-button-dynamic-labels-and-custom-pages',
element: <UserButtonCustomDynamicLabelsAndCustomPages />,
},
{
path: '/custom-user-button-trigger',
element: <UserButtonCustomTrigger />,
Expand Down
12 changes: 8 additions & 4 deletions integration/testUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ const createExpectPageObject = ({ page }: TestArgs) => {
expect(redirect.status()).toBe(307);
expect(redirect.headers()['x-clerk-auth-status']).toContain('handshake');
},
toBeSignedOut: () => {
return page.waitForFunction(() => {
return !window.Clerk?.user;
});
toBeSignedOut: (args?: { timeOut: number }) => {
return page.waitForFunction(
() => {
return !window.Clerk?.user;
},
null,
{ timeout: args?.timeOut },
);
},
toBeSignedIn: async () => {
return page.waitForFunction(() => {
Expand Down
24 changes: 24 additions & 0 deletions integration/testUtils/usersService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export type UserService = {
createBapiUser: (fakeUser: FakeUser) => Promise<User>;
deleteIfExists: (opts: { id?: string; email?: string }) => Promise<void>;
createFakeOrganization: (userId: string) => Promise<FakeOrganization>;
getUser: (opts: { id?: string; email?: string }) => Promise<User | undefined>;
};

/**
Expand Down Expand Up @@ -116,6 +117,29 @@ export const createUserService = (clerkClient: ClerkClient) => {

await clerkClient.users.deleteUser(id);
},
getUser: async (opts: { id?: string; email?: string }) => {
if (opts.id) {
try {
const user = await clerkClient.users.getUser(opts.id);
return user;
} catch (err) {
console.log(`Error fetching user "${opts.id}": ${err.message}`);
return;
}
}

if (opts.email) {
const { data: users } = await clerkClient.users.getUserList({ emailAddress: [opts.email] });
if (users.length > 0) {
return users[0];
} else {
console.log(`User "${opts.email}" does not exist!`);
return;
}
}

throw new Error('Either id or email must be provided');
},
createFakeOrganization: async userId => {
const name = faker.animal.dog();
const organization = await clerkClient.organizations.createOrganization({
Expand Down
68 changes: 68 additions & 0 deletions integration/tests/custom-pages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const CUSTOM_PROFILE_PAGE = '/custom-user-profile';
const CUSTOM_BUTTON_PAGE = '/custom-user-button';
const CUSTOM_BUTTON_TRIGGER_PAGE = '/custom-user-button-trigger';
const CUSTOM_BUTTON_DYNAMIC_LABELS_PAGE = '/custom-user-button-dynamic-labels';
const CUSTOM_BUTTON_DYNAMIC_LABELS_AND_CUSTOM_PAGES_PAGE = '/custom-user-button-dynamic-labels-and-custom-pages';

async function waitForMountedComponent(
component: 'UserButton' | 'UserProfile',
Expand Down Expand Up @@ -375,5 +376,72 @@ testAgainstRunningApps({ withPattern: ['react.vite.withEmailCodes'] })(
await expect(languageButton).toHaveText('🌍Language: EN');
});
});

test.describe('User Button with dynamic labels and custom page', () => {
test('click Chat is OFF and ensure that state has been changed', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.po.signIn.goTo();
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_BUTTON_DYNAMIC_LABELS_AND_CUSTOM_PAGES_PAGE);
await u.po.userButton.waitForMounted();
await u.po.userButton.toggleTrigger();
await u.po.userButton.waitForPopover();

const pagesContainer = u.page.locator('div.cl-userButtonPopoverActions__multiSession').first();
const buttons = await pagesContainer.locator('button').all();

expect(buttons.length).toBe(9);

const expectedTexts = [
'🌐Chat is OFF',
'🌐Theme: ☀️ Light',
'🌐Notifications 🔕 OFF',
'🌍Language: EN',
'Manage account',
'Sign out',
'🌐Visit Clerk',
'🌐Visit User page',
'🔔Custom Alert',
];

for (let i = 0; i < buttons.length; i++) {
await expect(buttons[i]).toHaveText(expectedTexts[i]);
}

const chatButton = buttons[0];
const notificationsButton = buttons[2];
const languageButton = buttons[3];
const manageAccountButton = buttons[4];

// Test chat toggle
await chatButton.click();
await u.po.userButton.toggleTrigger();
await u.po.userButton.waitForPopover();
await expect(chatButton).toHaveText('🌐Chat is ON');
await expect(languageButton).toHaveText('🌍Language: EN');

await notificationsButton.click();
await u.po.userButton.toggleTrigger();
await u.po.userButton.waitForPopover();
await expect(notificationsButton).toHaveText('🌐Notifications 🔔 ON');
await expect(chatButton).toHaveText('🌐Chat is ON');
await expect(languageButton).toHaveText('🌍Language: EN');

await manageAccountButton.click();
await u.po.userProfile.waitForMounted();

const userProfilePageButtons = await u.page.locator('button.cl-navbarButton__custom-page-0').all();
const [notificationsPage] = userProfilePageButtons;
await expect(notificationsPage.locator('div.cl-navbarButtonIcon__custom-page-0')).toHaveText('🔔');

await notificationsPage.click();

const orderSent = page.locator('h1[data-page="notifications-page"]');
await orderSent.waitFor({ state: 'attached' });
});
});
},
);
4 changes: 2 additions & 2 deletions integration/tests/nuxt/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ test.describe('custom middleware @nuxt', () => {
)
.addFile(
'server/middleware/clerk.js',
() => `import { clerkMiddleware, createRouteMatcher } from '@clerk/nuxt/server';
() => `import { clerkMiddleware, createRouteMatcher, getAuth } from '@clerk/nuxt/server';
export default clerkMiddleware((event) => {
const { userId } = event.context.auth
const { userId } = getAuth(event);
const isProtectedRoute = createRouteMatcher(['/api/me']);
if (!userId && isProtectedRoute(event)) {
Expand Down
Loading

0 comments on commit 14552ba

Please sign in to comment.