diff --git a/.changeset/moody-fans-kneel.md b/.changeset/cyan-countries-do.md similarity index 100% rename from .changeset/moody-fans-kneel.md rename to .changeset/cyan-countries-do.md diff --git a/.changeset/fresh-forks-talk.md b/.changeset/fresh-forks-talk.md new file mode 100644 index 00000000000..7b63f2b72d5 --- /dev/null +++ b/.changeset/fresh-forks-talk.md @@ -0,0 +1,7 @@ +--- +"@clerk/clerk-js": patch +"@clerk/backend": patch +"@clerk/types": patch +--- + +Conditionally renders identification sections on `UserProfile` based on the SAML connection configuration for disabling additional identifiers. diff --git a/.changeset/many-baboons-wash.md b/.changeset/many-baboons-wash.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/many-baboons-wash.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/tame-forks-type.md b/.changeset/tame-forks-type.md new file mode 100644 index 00000000000..f3a28812a6a --- /dev/null +++ b/.changeset/tame-forks-type.md @@ -0,0 +1,6 @@ +--- +"@clerk/backend": patch +"@clerk/types": patch +--- + +Introduces the CRUD of organization domains under the `organizations` API. diff --git a/.changeset/ten-months-kick.md b/.changeset/ten-months-kick.md deleted file mode 100644 index 619ea228614..00000000000 --- a/.changeset/ten-months-kick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@clerk/nextjs": minor ---- - -Allows access to request object to dynamically define `clerkMiddleware` options diff --git a/.changeset/twenty-weeks-accept.md b/.changeset/twenty-weeks-accept.md deleted file mode 100644 index f16c7a40e7b..00000000000 --- a/.changeset/twenty-weeks-accept.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@clerk/clerk-js": patch ---- - -Restore behavior of MetaMask compatible Web3 wallets. Before, even if a user didn't use the MetaMask browser extension but a compatible one, such as Rabby Wallet, it was possible to use it as they share the same API to authenticate themselves. This behavior stopped working when we added support for EIP6963 regarding handling multiple injected providers. This commit restores the previous behavior by using the existing injected provider if there is a single one diff --git a/integration/tests/handshake.test.ts b/integration/tests/handshake.test.ts index ffc2b638196..887b45b0a40 100644 --- a/integration/tests/handshake.test.ts +++ b/integration/tests/handshake.test.ts @@ -72,7 +72,7 @@ test.describe('Client handshake @generic', () => { await new Promise(resolve => jwksServer.close(() => resolve())); }); - test('Test standard signed-in - dev', async () => { + test('standard signed-in - dev', async () => { const config = generateConfig({ mode: 'test' }); const { token, claims } = config.generateToken({ state: 'active' }); const clientUat = claims.iat; @@ -88,7 +88,7 @@ test.describe('Client handshake @generic', () => { expect(res.status).toBe(200); }); - test('Test standard signed-in - authorization header - dev', async () => { + test('standard signed-in - authorization header - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -107,7 +107,7 @@ test.describe('Client handshake @generic', () => { expect(res.status).toBe(200); }); - test('Test standard signed-in - prod', async () => { + test('standard signed-in - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -125,7 +125,7 @@ test.describe('Client handshake @generic', () => { expect(res.status).toBe(200); }); - test('Test standard signed-in - authorization header - prod', async () => { + test('standard signed-in - authorization header - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -143,7 +143,7 @@ test.describe('Client handshake @generic', () => { expect(res.status).toBe(200); }); - test('Test expired session token - dev', async () => { + test('expired session token - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -162,11 +162,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false${devBrowserQuery}`, + )}&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie${devBrowserQuery}`, ); }); - test('Test expired session token - prod', async () => { + test('expired session token - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -185,11 +185,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false`, + )}&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie`, ); }); - test('Test expired session token - authorization header - prod', async () => { + test('expired session token - authorization header - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -209,11 +209,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false`, + )}&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie`, ); }); - test('Test early session token - dev', async () => { + test('early session token - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -232,11 +232,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false${devBrowserQuery}`, + )}&suffixed_cookies=false&__clerk_hs_reason=session-token-nbf${devBrowserQuery}`, ); }); - test('Test early session token - authorization header - dev', async () => { + test('early session token - authorization header - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -256,11 +256,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false${devBrowserQuery}`, + )}&suffixed_cookies=false&__clerk_hs_reason=session-token-nbf${devBrowserQuery}`, ); }); - test('Test proxyUrl - dev', async () => { + test('proxyUrl - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -280,11 +280,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://example.com/clerk/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false${devBrowserQuery}`, + )}&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie${devBrowserQuery}`, ); }); - test('Test proxyUrl - prod', async () => { + test('proxyUrl - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -304,11 +304,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://example.com/clerk/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false`, + )}&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie`, ); }); - test('Test domain - dev', async () => { + test('domain - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -328,11 +328,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false${devBrowserQuery}`, + )}&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie${devBrowserQuery}`, ); }); - test('Test domain - prod', async () => { + test('domain - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -352,11 +352,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://clerk.example.com/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false`, + )}&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie`, ); }); - test('Test missing session token, positive uat - dev', async () => { + test('missing session token, positive uat - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -373,11 +373,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false${devBrowserQuery}`, + )}&suffixed_cookies=false&__clerk_hs_reason=client-uat-but-no-session-token${devBrowserQuery}`, ); }); - test('Test missing session token, positive uat - prod', async () => { + test('missing session token, positive uat - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -394,11 +394,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false`, + )}&suffixed_cookies=false&__clerk_hs_reason=client-uat-but-no-session-token`, ); }); - test('Test missing session token, 0 uat (indicating signed out) - dev', async () => { + test('missing session token, 0 uat (indicating signed out) - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -414,7 +414,7 @@ test.describe('Client handshake @generic', () => { expect(res.status).toBe(200); }); - test('Test missing session token, 0 uat (indicating signed out) - prod', async () => { + test('missing session token, 0 uat (indicating signed out) - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -430,7 +430,7 @@ test.describe('Client handshake @generic', () => { expect(res.status).toBe(200); }); - test('Test missing session token, missing uat (indicating signed out) - dev', async () => { + test('missing session token, missing uat (indicating signed out) - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -446,7 +446,7 @@ test.describe('Client handshake @generic', () => { expect(res.status).toBe(200); }); - test('Test missing session token, missing uat (indicating signed out) - prod', async () => { + test('missing session token, missing uat (indicating signed out) - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -461,7 +461,7 @@ test.describe('Client handshake @generic', () => { expect(res.status).toBe(200); }); - test('Test signed out satellite no sec-fetch-dest=document - prod', async () => { + test('signed out satellite no sec-fetch-dest=document - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -477,7 +477,7 @@ test.describe('Client handshake @generic', () => { expect(res.status).toBe(200); }); - test('Test signed out satellite with sec-fetch-dest=document - prod', async () => { + test('signed out satellite with sec-fetch-dest=document - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -495,11 +495,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://clerk.example.com/v1/client/handshake?redirect_url=${encodeURIComponent( app.serverUrl + '/', - )}&suffixed_cookies=false`, + )}&suffixed_cookies=false&__clerk_hs_reason=satellite-needs-syncing`, ); }); - test('Test signed out satellite - dev', async () => { + test('signed out satellite - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -516,7 +516,7 @@ test.describe('Client handshake @generic', () => { expect(res.status).toBe(200); }); - test('Test missing session token, missing uat (indicating signed out), missing devbrowser - dev', async () => { + test('missing session token, missing uat (indicating signed out), missing devbrowser - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -532,11 +532,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false`, + )}&suffixed_cookies=false&__clerk_hs_reason=dev-browser-missing`, ); }); - test('Test redirect url - path and qs - dev', async () => { + test('redirect url - path and qs - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -555,11 +555,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}hello%3Ffoo%3Dbar&suffixed_cookies=false${devBrowserQuery}`, + )}hello%3Ffoo%3Dbar&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie${devBrowserQuery}`, ); }); - test('Test redirect url - path and qs - prod', async () => { + test('redirect url - path and qs - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -578,11 +578,11 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}hello%3Ffoo%3Dbar&suffixed_cookies=false`, + )}hello%3Ffoo%3Dbar&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie`, ); }); - test('Test redirect url - proxy - dev', async () => { + test('redirect url - proxy - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -601,11 +601,11 @@ test.describe('Client handshake @generic', () => { }); expect(res.status).toBe(307); expect(res.headers.get('location')).toBe( - `https://${config.pkHost}/v1/client/handshake?redirect_url=https%3A%2F%2Fexample.com%2Fhello%3Ffoo%3Dbar&suffixed_cookies=false${devBrowserQuery}`, + `https://${config.pkHost}/v1/client/handshake?redirect_url=https%3A%2F%2Fexample.com%2Fhello%3Ffoo%3Dbar&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie${devBrowserQuery}`, ); }); - test('Test redirect url - proxy - prod', async () => { + test('redirect url - proxy - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -624,11 +624,11 @@ test.describe('Client handshake @generic', () => { }); expect(res.status).toBe(307); expect(res.headers.get('location')).toBe( - `https://${config.pkHost}/v1/client/handshake?redirect_url=https%3A%2F%2Fexample.com%2Fhello%3Ffoo%3Dbar&suffixed_cookies=false`, + `https://${config.pkHost}/v1/client/handshake?redirect_url=https%3A%2F%2Fexample.com%2Fhello%3Ffoo%3Dbar&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie`, ); }); - test('Test redirect url - proxy with port - dev', async () => { + test('redirect url - proxy with port - dev', async () => { const config = generateConfig({ mode: 'test', }); @@ -647,11 +647,11 @@ test.describe('Client handshake @generic', () => { }); expect(res.status).toBe(307); expect(res.headers.get('location')).toBe( - `https://${config.pkHost}/v1/client/handshake?redirect_url=https%3A%2F%2Fexample.com%3A3213%2Fhello%3Ffoo%3Dbar&suffixed_cookies=false${devBrowserQuery}`, + `https://${config.pkHost}/v1/client/handshake?redirect_url=https%3A%2F%2Fexample.com%3A3213%2Fhello%3Ffoo%3Dbar&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie${devBrowserQuery}`, ); }); - test('Test redirect url - proxy with port - prod', async () => { + test('redirect url - proxy with port - prod', async () => { const config = generateConfig({ mode: 'live', }); @@ -670,7 +670,7 @@ test.describe('Client handshake @generic', () => { }); expect(res.status).toBe(307); expect(res.headers.get('location')).toBe( - `https://${config.pkHost}/v1/client/handshake?redirect_url=https%3A%2F%2Fexample.com%3A3213%2Fhello%3Ffoo%3Dbar&suffixed_cookies=false`, + `https://${config.pkHost}/v1/client/handshake?redirect_url=https%3A%2F%2Fexample.com%3A3213%2Fhello%3Ffoo%3Dbar&suffixed_cookies=false&__clerk_hs_reason=session-token-expired-refresh-non-eligible-no-refresh-cookie`, ); }); @@ -799,7 +799,7 @@ test.describe('Client handshake @generic', () => { expect(res.headers.get('location')).toBe( `https://${config.pkHost}/v1/client/handshake?redirect_url=${encodeURIComponent( `${app.serverUrl}/`, - )}&suffixed_cookies=false&__clerk_db_jwt=asdf`, + )}&suffixed_cookies=false&__clerk_hs_reason=dev-browser-sync&__clerk_db_jwt=asdf`, ); }); diff --git a/package-lock.json b/package-lock.json index 007dca13e05..dfc59ff6315 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39842,12 +39842,12 @@ }, "packages/astro": { "name": "@clerk/astro", - "version": "1.3.5", + "version": "1.3.8", "license": "MIT", "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "nanoid": "5.0.7", "nanostores": "0.11.3" }, @@ -39894,11 +39894,11 @@ }, "packages/backend": { "name": "@clerk/backend", - "version": "1.13.1", + "version": "1.13.4", "license": "MIT", "dependencies": { - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "cookie": "0.5.0", "snakecase-keys": "5.4.4", "tslib": "2.4.1" @@ -40486,12 +40486,12 @@ }, "packages/chrome-extension": { "name": "@clerk/chrome-extension", - "version": "1.3.7", + "version": "1.3.10", "license": "MIT", "dependencies": { - "@clerk/clerk-js": "5.22.2", - "@clerk/clerk-react": "5.9.1", - "@clerk/shared": "2.8.1", + "@clerk/clerk-js": "5.23.0", + "@clerk/clerk-react": "5.9.3", + "@clerk/shared": "2.8.3", "webextension-polyfill": "^0.10.0" }, "devDependencies": { @@ -40535,12 +40535,12 @@ }, "packages/clerk-js": { "name": "@clerk/clerk-js", - "version": "5.22.2", + "version": "5.23.0", "license": "MIT", "dependencies": { - "@clerk/localizations": "3.0.3", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/localizations": "3.0.6", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "@coinbase/wallet-sdk": "4.0.4", "@emotion/cache": "11.11.0", "@emotion/react": "11.11.1", @@ -41458,10 +41458,10 @@ }, "packages/elements": { "name": "@clerk/elements", - "version": "0.15.4", + "version": "0.15.6", "license": "MIT", "dependencies": { - "@clerk/types": "^4.21.0", + "@clerk/types": "^4.22.0", "@radix-ui/react-form": "^0.1.0", "@radix-ui/react-slot": "^1.1.0", "@xstate/react": "^4.1.1", @@ -41469,9 +41469,9 @@ "xstate": "^5.15.0" }, "devDependencies": { - "@clerk/clerk-react": "5.9.1", + "@clerk/clerk-react": "5.9.3", "@clerk/eslint-config-custom": "*", - "@clerk/shared": "2.8.1", + "@clerk/shared": "2.8.3", "@statelyai/inspect": "^0.4.0", "@types/node": "^18.19.33", "@types/react": "*", @@ -42171,13 +42171,13 @@ }, "packages/expo": { "name": "@clerk/clerk-expo", - "version": "2.2.13", + "version": "2.2.16", "license": "MIT", "dependencies": { - "@clerk/clerk-js": "5.22.2", - "@clerk/clerk-react": "5.9.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/clerk-js": "5.23.0", + "@clerk/clerk-react": "5.9.3", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "base-64": "^1.0.0", "react-native-url-polyfill": "2.0.0", "tslib": "2.4.1" @@ -42456,12 +42456,12 @@ }, "packages/express": { "name": "@clerk/express", - "version": "0.1.2", + "version": "1.0.1", "license": "MIT", "dependencies": { - "@clerk/backend": "^1.13.1", - "@clerk/shared": "^2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "^1.13.4", + "@clerk/shared": "^2.8.3", + "@clerk/types": "4.22.0", "tslib": "2.4.1" }, "devDependencies": { @@ -42476,6 +42476,9 @@ }, "engines": { "node": ">=18.17.0" + }, + "peerDependencies": { + "express": "^4.17.0 || ^5.0.0" } }, "packages/express/node_modules/cookie": { @@ -42697,12 +42700,12 @@ }, "packages/fastify": { "name": "@clerk/fastify", - "version": "1.0.44", + "version": "1.0.47", "license": "MIT", "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "cookies": "0.8.0" }, "devDependencies": { @@ -42721,10 +42724,10 @@ }, "packages/localizations": { "name": "@clerk/localizations", - "version": "3.0.3", + "version": "3.0.6", "license": "MIT", "dependencies": { - "@clerk/types": "4.21.0" + "@clerk/types": "4.22.0" }, "devDependencies": { "@clerk/eslint-config-custom": "*", @@ -42737,13 +42740,13 @@ }, "packages/nextjs": { "name": "@clerk/nextjs", - "version": "5.5.5", + "version": "5.6.2", "license": "MIT", "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/clerk-react": "5.9.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/clerk-react": "5.9.3", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "crypto-js": "4.2.0", "server-only": "0.0.1", "tslib": "2.4.1" @@ -42877,16 +42880,16 @@ }, "packages/react": { "name": "@clerk/clerk-react", - "version": "5.9.1", + "version": "5.9.3", "license": "MIT", "dependencies": { - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "tslib": "2.4.1" }, "devDependencies": { "@clerk/eslint-config-custom": "*", - "@clerk/themes": "2.1.30", + "@clerk/themes": "2.1.32", "@types/node": "^18.19.33", "@types/react": "*", "@types/react-dom": "*", @@ -42907,13 +42910,13 @@ }, "packages/remix": { "name": "@clerk/remix", - "version": "4.2.28", + "version": "4.2.31", "license": "MIT", "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/clerk-react": "5.9.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/clerk-react": "5.9.3", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "cookie": "0.5.0", "tslib": "2.4.1" }, @@ -42948,12 +42951,12 @@ }, "packages/sdk-node": { "name": "@clerk/clerk-sdk-node", - "version": "5.0.41", + "version": "5.0.44", "license": "MIT", "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "tslib": "2.4.1" }, "devDependencies": { @@ -42990,11 +42993,11 @@ }, "packages/shared": { "name": "@clerk/shared", - "version": "2.8.1", + "version": "2.8.3", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@clerk/types": "4.21.0", + "@clerk/types": "4.22.0", "glob-to-regexp": "0.4.1", "js-cookie": "3.0.5", "std-env": "^3.7.0", @@ -43689,13 +43692,13 @@ }, "packages/tanstack-start": { "name": "@clerk/tanstack-start", - "version": "0.4.4", + "version": "0.4.7", "license": "MIT", "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/clerk-react": "5.9.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/clerk-react": "5.9.3", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "tslib": "2.4.1" }, "devDependencies": { @@ -44526,12 +44529,12 @@ }, "packages/testing": { "name": "@clerk/testing", - "version": "1.3.2", + "version": "1.3.5", "license": "MIT", "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "dotenv": "16.4.5" }, "devDependencies": { @@ -44560,10 +44563,10 @@ }, "packages/themes": { "name": "@clerk/themes", - "version": "2.1.30", + "version": "2.1.32", "license": "MIT", "dependencies": { - "@clerk/types": "4.21.0", + "@clerk/types": "4.22.0", "tslib": "2.4.1" }, "devDependencies": { @@ -44580,7 +44583,7 @@ }, "packages/types": { "name": "@clerk/types", - "version": "4.21.0", + "version": "4.22.0", "license": "MIT", "dependencies": { "csstype": "3.1.1" diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md index 3630840e13f..715ba4133b4 100644 --- a/packages/astro/CHANGELOG.md +++ b/packages/astro/CHANGELOG.md @@ -1,5 +1,32 @@ # @clerk/astro +## 1.3.8 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + - @clerk/backend@1.13.4 + - @clerk/shared@2.8.3 + +## 1.3.7 + +### Patch Changes + +- Add `@clerk/astro` to Astro integrations list page ([#4194](https://github.com/clerk/javascript/pull/4194)) by [@wobsoriano](https://github.com/wobsoriano) + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`418be2fdb`](https://github.com/clerk/javascript/commit/418be2fdb558bb5c85d7be491945935b44cad681), [`c59636a1a`](https://github.com/clerk/javascript/commit/c59636a1aca67be7d6732d281cec307ed456678b), [`5c18671f1`](https://github.com/clerk/javascript/commit/5c18671f158f8077f822877ce5c1fa192199aeda), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529), [`e0ca9dc94`](https://github.com/clerk/javascript/commit/e0ca9dc94fa68f3d3db5d2433fa6b85d800d4ca2)]: + - @clerk/shared@2.8.2 + - @clerk/types@4.21.1 + - @clerk/backend@1.13.3 + +## 1.3.6 + +### Patch Changes + +- Updated dependencies [[`02babaccb`](https://github.com/clerk/javascript/commit/02babaccb648fa4e22f38cc0f572d44f82b09f78)]: + - @clerk/backend@1.13.2 + ## 1.3.5 ### Patch Changes diff --git a/packages/astro/package.json b/packages/astro/package.json index 9ceed8b8fad..6b9eef3aae6 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,7 +1,7 @@ { "name": "@clerk/astro", "description": "Clerk SDK for Astro", - "version": "1.3.5", + "version": "1.3.8", "type": "module", "license": "MIT", "author": "Clerk", @@ -20,7 +20,9 @@ "astro-integration", "clerk", "typescript", - "passwordless" + "passwordless", + "astro-component", + "withastro" ], "sideEffects": false, "bugs": { @@ -79,9 +81,9 @@ } }, "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "nanoid": "5.0.7", "nanostores": "0.11.3" }, diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 714b4337e30..8212445dadf 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,35 @@ # Change Log +## 1.13.4 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + - @clerk/shared@2.8.3 + +## 1.13.3 + +### Patch Changes + +- Introduce `includeMembersCount` parameter to `getOrganization`, allowing to retrieve an organization with `membersCount`. ([#4196](https://github.com/clerk/javascript/pull/4196)) by [@LauraBeatris](https://github.com/LauraBeatris) + +- Improve debugging error reasons. ([#4205](https://github.com/clerk/javascript/pull/4205)) by [@anagstef](https://github.com/anagstef) + +- Drop the `__clerk_refresh` debugging query param and use only the `__clerk_hs_reason` param for all scenarios. ([#4213](https://github.com/clerk/javascript/pull/4213)) by [@anagstef](https://github.com/anagstef) + +- Introduce more refresh token error reasons. ([#4193](https://github.com/clerk/javascript/pull/4193)) by [@anagstef](https://github.com/anagstef) + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529)]: + - @clerk/shared@2.8.2 + - @clerk/types@4.21.1 + +## 1.13.2 + +### Patch Changes + +- Add the handshake reason as a query param for observability. ([#4184](https://github.com/clerk/javascript/pull/4184)) by [@anagstef](https://github.com/anagstef) + ## 1.13.1 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 049e43ebf0d..7e1238118f7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/backend", - "version": "1.13.1", + "version": "1.13.4", "description": "Clerk Backend SDK - REST Client for Backend API & JWT verification utilities", "homepage": "https://clerk.com/", "bugs": { @@ -95,8 +95,8 @@ "test:cloudflare-workerd": "tests/cloudflare-workerd/run.sh" }, "dependencies": { - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "cookie": "0.5.0", "snakecase-keys": "5.4.4", "tslib": "2.4.1" diff --git a/packages/backend/src/api/endpoints/OrganizationApi.ts b/packages/backend/src/api/endpoints/OrganizationApi.ts index 020eb200f0c..b86e2cb2380 100644 --- a/packages/backend/src/api/endpoints/OrganizationApi.ts +++ b/packages/backend/src/api/endpoints/OrganizationApi.ts @@ -1,9 +1,10 @@ -import type { ClerkPaginationRequest } from '@clerk/types'; +import type { ClerkPaginationRequest, OrganizationEnrollmentMode } from '@clerk/types'; import runtime from '../../runtime'; import { joinPaths } from '../../util/path'; import type { Organization, + OrganizationDomain, OrganizationInvitation, OrganizationInvitationStatus, OrganizationMembership, @@ -34,7 +35,9 @@ type CreateParams = { maxAllowedMemberships?: number; } & MetadataParams; -type GetOrganizationParams = { organizationId: string } | { slug: string }; +type GetOrganizationParams = ({ organizationId: string } | { slug: string }) & { + includeMembersCount?: boolean; +}; type UpdateParams = { name?: string; @@ -97,6 +100,29 @@ type RevokeOrganizationInvitationParams = { requestingUserId: string; }; +type GetOrganizationDomainListParams = { + organizationId: string; + limit?: number; + offset?: number; +}; + +type CreateOrganizationDomainParams = { + organizationId: string; + name: string; + enrollmentMode: OrganizationEnrollmentMode; + verified?: boolean; +}; + +type UpdateOrganizationDomainParams = { + organizationId: string; + domainId: string; +} & Partial; + +type DeleteOrganizationDomainParams = { + organizationId: string; + domainId: string; +}; + export class OrganizationAPI extends AbstractAPI { public async getOrganizationList(params?: GetOrganizationListParams) { return this.request>({ @@ -115,12 +141,16 @@ export class OrganizationAPI extends AbstractAPI { } public async getOrganization(params: GetOrganizationParams) { + const { includeMembersCount } = params; const organizationIdOrSlug = 'organizationId' in params ? params.organizationId : params.slug; this.requireId(organizationIdOrSlug); return this.request({ method: 'GET', path: joinPaths(basePath, organizationIdOrSlug), + queryParams: { + includeMembersCount, + }, }); } @@ -279,4 +309,53 @@ export class OrganizationAPI extends AbstractAPI { }, }); } + + public async getOrganizationDomainList(params: GetOrganizationDomainListParams) { + const { organizationId, limit, offset } = params; + this.requireId(organizationId); + + return this.request>({ + method: 'GET', + path: joinPaths(basePath, organizationId, 'domains'), + queryParams: { limit, offset }, + }); + } + + public async createOrganizationDomain(params: CreateOrganizationDomainParams) { + const { organizationId, name, enrollmentMode, verified = true } = params; + this.requireId(organizationId); + + return this.request({ + method: 'POST', + path: joinPaths(basePath, organizationId, 'domains'), + bodyParams: { + name, + enrollmentMode, + verified, + }, + }); + } + + public async updateOrganizationDomain(params: UpdateOrganizationDomainParams) { + const { organizationId, domainId, ...bodyParams } = params; + this.requireId(organizationId); + this.requireId(domainId); + + return this.request({ + method: 'PATCH', + path: joinPaths(basePath, organizationId, 'domains', domainId), + bodyParams, + }); + } + + public async deleteOrganizationDomain(params: DeleteOrganizationDomainParams) { + const { organizationId, domainId } = params; + this.requireId(organizationId); + this.requireId(domainId); + + return this.request({ + method: 'DELETE', + path: joinPaths(basePath, organizationId, 'domains', domainId), + }); + } } diff --git a/packages/backend/src/api/resources/JSON.ts b/packages/backend/src/api/resources/JSON.ts index fbb3fcf57be..7b769f4495e 100644 --- a/packages/backend/src/api/resources/JSON.ts +++ b/packages/backend/src/api/resources/JSON.ts @@ -114,6 +114,7 @@ export interface SamlAccountJSON extends ClerkResourceJSON { first_name: string; last_name: string; verification: VerificationJSON | null; + saml_connection: SamlAccountConnectionJSON | null; } export interface IdentificationLinkJSON extends ClerkResourceJSON { @@ -399,3 +400,17 @@ export interface PermissionJSON extends ClerkResourceJSON { created_at: number; updated_at: number; } + +export interface SamlAccountConnectionJSON extends ClerkResourceJSON { + id: string; + name: string; + domain: string; + active: boolean; + provider: string; + sync_user_attributes: boolean; + allow_subdomains: boolean; + allow_idp_initiated: boolean; + disable_additional_identifications: boolean; + created_at: number; + updated_at: number; +} diff --git a/packages/backend/src/api/resources/OrganizationDomain.ts b/packages/backend/src/api/resources/OrganizationDomain.ts new file mode 100644 index 00000000000..c9baddeee14 --- /dev/null +++ b/packages/backend/src/api/resources/OrganizationDomain.ts @@ -0,0 +1,33 @@ +import type { OrganizationDomainJSON, OrganizationEnrollmentMode } from '@clerk/types'; + +import { OrganizationDomainVerification } from './Verification'; + +export class OrganizationDomain { + constructor( + readonly id: string, + readonly organizationId: string, + readonly name: string, + readonly enrollmentMode: OrganizationEnrollmentMode, + readonly verification: OrganizationDomainVerification | null, + readonly totalPendingInvitations: number, + readonly totalPendingSuggestions: number, + readonly createdAt: number, + readonly updatedAt: number, + readonly affiliationEmailAddress: string | null, + ) {} + + static fromJSON(data: OrganizationDomainJSON) { + return new OrganizationDomain( + data.id, + data.organization_id, + data.name, + data.enrollment_mode, + data.verification && OrganizationDomainVerification.fromJSON(data.verification), + data.total_pending_invitations, + data.total_pending_suggestions, + data.created_at, + data.updated_at, + data.affiliation_email_address, + ); + } +} diff --git a/packages/backend/src/api/resources/SamlAccount.ts b/packages/backend/src/api/resources/SamlAccount.ts index 1c4a06f5a78..ca74d751954 100644 --- a/packages/backend/src/api/resources/SamlAccount.ts +++ b/packages/backend/src/api/resources/SamlAccount.ts @@ -1,4 +1,5 @@ import type { SamlAccountJSON } from './JSON'; +import { SamlAccountConnection } from './SamlConnection'; import { Verification } from './Verification'; export class SamlAccount { @@ -11,6 +12,7 @@ export class SamlAccount { readonly firstName: string, readonly lastName: string, readonly verification: Verification | null, + readonly samlConnection: SamlAccountConnection | null, ) {} static fromJSON(data: SamlAccountJSON): SamlAccount { @@ -23,6 +25,7 @@ export class SamlAccount { data.first_name, data.last_name, data.verification && Verification.fromJSON(data.verification), + data.saml_connection && SamlAccountConnection.fromJSON(data.saml_connection), ); } } diff --git a/packages/backend/src/api/resources/SamlConnection.ts b/packages/backend/src/api/resources/SamlConnection.ts index 90695beed50..d32f0495f48 100644 --- a/packages/backend/src/api/resources/SamlConnection.ts +++ b/packages/backend/src/api/resources/SamlConnection.ts @@ -1,4 +1,4 @@ -import type { AttributeMappingJSON, SamlConnectionJSON } from './JSON'; +import type { AttributeMappingJSON, SamlAccountConnectionJSON, SamlConnectionJSON } from './JSON'; export class SamlConnection { constructor( @@ -49,6 +49,35 @@ export class SamlConnection { } } +export class SamlAccountConnection { + constructor( + readonly id: string, + readonly name: string, + readonly domain: string, + readonly active: boolean, + readonly provider: string, + readonly syncUserAttributes: boolean, + readonly allowSubdomains: boolean, + readonly allowIdpInitiated: boolean, + readonly createdAt: number, + readonly updatedAt: number, + ) {} + static fromJSON(data: SamlAccountConnectionJSON): SamlAccountConnection { + return new SamlAccountConnection( + data.id, + data.name, + data.domain, + data.active, + data.provider, + data.sync_user_attributes, + data.allow_subdomains, + data.allow_idp_initiated, + data.created_at, + data.updated_at, + ); + } +} + class AttributeMapping { constructor( readonly userId: string, diff --git a/packages/backend/src/api/resources/Verification.ts b/packages/backend/src/api/resources/Verification.ts index c6015812dd9..35cbfb4d369 100644 --- a/packages/backend/src/api/resources/Verification.ts +++ b/packages/backend/src/api/resources/Verification.ts @@ -1,3 +1,5 @@ +import type { OrganizationDomainVerificationJSON } from '@clerk/types'; + import type { VerificationJSON } from './JSON'; export class Verification { @@ -21,3 +23,16 @@ export class Verification { ); } } + +export class OrganizationDomainVerification { + constructor( + readonly status: string, + readonly strategy: string, + readonly attempts: number | null = null, + readonly expireAt: number | null = null, + ) {} + + static fromJSON(data: OrganizationDomainVerificationJSON): OrganizationDomainVerification { + return new OrganizationDomainVerification(data.status, data.strategy, data.attempts, data.expires_at); + } +} diff --git a/packages/backend/src/api/resources/index.ts b/packages/backend/src/api/resources/index.ts index 4652a7e8b2a..9a0ec1a3766 100644 --- a/packages/backend/src/api/resources/index.ts +++ b/packages/backend/src/api/resources/index.ts @@ -44,3 +44,5 @@ export type { WebhookEvent, WebhookEventType, } from './Webhooks'; + +export * from './OrganizationDomain'; diff --git a/packages/backend/src/constants.ts b/packages/backend/src/constants.ts index 9319d83511e..9b7d92e83e1 100644 --- a/packages/backend/src/constants.ts +++ b/packages/backend/src/constants.ts @@ -31,6 +31,7 @@ const QueryParameters = { Handshake: Cookies.Handshake, HandshakeHelp: '__clerk_help', LegacyDevBrowser: '__dev_session', + HandshakeReason: '__clerk_hs_reason', } as const; const Headers = { diff --git a/packages/backend/src/errors.ts b/packages/backend/src/errors.ts index 111cf4d8ec1..90cfdc343a3 100644 --- a/packages/backend/src/errors.ts +++ b/packages/backend/src/errors.ts @@ -13,6 +13,7 @@ export const TokenVerificationErrorReason = { TokenInvalidAuthorizedParties: 'token-invalid-authorized-parties', TokenInvalidSignature: 'token-invalid-signature', TokenNotActiveYet: 'token-not-active-yet', + TokenIatInTheFuture: 'token-iat-in-the-future', TokenVerificationFailed: 'token-verification-failed', InvalidSecretKey: 'secret-key-invalid', LocalJWKMissing: 'jwk-local-missing', diff --git a/packages/backend/src/jwt/assertions.ts b/packages/backend/src/jwt/assertions.ts index f91e9b2636f..4153de625f4 100644 --- a/packages/backend/src/jwt/assertions.ts +++ b/packages/backend/src/jwt/assertions.ts @@ -162,7 +162,7 @@ export const assertIssuedAtClaim = (iat: number | undefined, clockSkewInMs: numb const postIssued = issuedAtDate.getTime() > currentDate.getTime() + clockSkewInMs; if (postIssued) { throw new TokenVerificationError({ - reason: TokenVerificationErrorReason.TokenNotActiveYet, + reason: TokenVerificationErrorReason.TokenIatInTheFuture, message: `JWT issued at date claim (iat) is in the future. Issued at date: ${issuedAtDate.toUTCString()}; Current date: ${currentDate.toUTCString()};`, }); } diff --git a/packages/backend/src/tokens/__tests__/request.test.ts b/packages/backend/src/tokens/__tests__/request.test.ts index 73a68c74baf..7cd1ff3f4a7 100644 --- a/packages/backend/src/tokens/__tests__/request.test.ts +++ b/packages/backend/src/tokens/__tests__/request.test.ts @@ -13,7 +13,7 @@ import { import runtime from '../../runtime'; import { jsonOk } from '../../util/testUtils'; import { AuthErrorReason, type AuthReason, AuthStatus, type RequestState } from '../authStatus'; -import { authenticateRequest } from '../request'; +import { authenticateRequest, RefreshTokenErrorReason } from '../request'; import type { AuthenticateRequestOptions } from '../types'; const PK_TEST = 'pk_test_Y2xlcmsuaW5zcGlyZWQucHVtYS03NC5sY2wuZGV2JA'; @@ -238,7 +238,9 @@ export default (QUnit: QUnit) => { const requestState = await authenticateRequest(mockRequestWithHeaderAuth(), mockOptions()); - assertHandshake(assert, requestState, { reason: AuthErrorReason.SessionTokenOutdated }); + assertHandshake(assert, requestState, { + reason: `${AuthErrorReason.SessionTokenExpired}-refresh-${RefreshTokenErrorReason.NonEligibleNoCookie}`, + }); assert.strictEqual(requestState.toAuth(), null); }); @@ -487,7 +489,7 @@ export default (QUnit: QUnit) => { mockOptions(), ); - assertHandshake(assert, requestState, { reason: AuthErrorReason.SessionTokenOutdated }); + assertHandshake(assert, requestState, { reason: AuthErrorReason.SessionTokenIATBeforeClientUAT }); assert.equal(requestState.message, ''); assert.strictEqual(requestState.toAuth(), null); }); @@ -554,7 +556,9 @@ export default (QUnit: QUnit) => { mockOptions(), ); - assertHandshake(assert, requestState, { reason: AuthErrorReason.SessionTokenOutdated }); + assertHandshake(assert, requestState, { + reason: `${AuthErrorReason.SessionTokenExpired}-refresh-${RefreshTokenErrorReason.NonEligibleNoCookie}`, + }); assert.true(/^JWT is expired/.test(requestState.message || '')); assert.strictEqual(requestState.toAuth(), null); }); diff --git a/packages/backend/src/tokens/authStatus.ts b/packages/backend/src/tokens/authStatus.ts index 78a7de9c129..f43cff75495 100644 --- a/packages/backend/src/tokens/authStatus.ts +++ b/packages/backend/src/tokens/authStatus.ts @@ -64,7 +64,10 @@ export const AuthErrorReason = { SatelliteCookieNeedsSyncing: 'satellite-needs-syncing', SessionTokenAndUATMissing: 'session-token-and-uat-missing', SessionTokenMissing: 'session-token-missing', - SessionTokenOutdated: 'session-token-outdated', + SessionTokenExpired: 'session-token-expired', + SessionTokenIATBeforeClientUAT: 'session-token-iat-before-client-uat', + SessionTokenNBF: 'session-token-nbf', + SessionTokenIatInTheFuture: 'session-token-iat-in-the-future', SessionTokenWithoutClientUAT: 'session-token-but-no-client-uat', UnexpectedError: 'unexpected-error', } as const; diff --git a/packages/backend/src/tokens/request.ts b/packages/backend/src/tokens/request.ts index 557fb11b9c7..4e31dc04f45 100644 --- a/packages/backend/src/tokens/request.ts +++ b/packages/backend/src/tokens/request.ts @@ -1,4 +1,5 @@ -import type { ApiClient } from '../api'; +import type { JwtPayload } from '@clerk/types'; + import { constants } from '../constants'; import type { TokenCarrier } from '../errors'; import { TokenVerificationError, TokenVerificationErrorReason } from '../errors'; @@ -15,6 +16,18 @@ import { verifyHandshakeToken } from './handshake'; import type { AuthenticateRequestOptions } from './types'; import { verifyToken } from './verify'; +export const RefreshTokenErrorReason = { + NonEligibleNoCookie: 'non-eligible-no-refresh-cookie', + NonEligibleNonGet: 'non-eligible-non-get', + InvalidSessionToken: 'invalid-session-token', + MissingApiClient: 'missing-api-client', + MissingSessionToken: 'missing-session-token', + MissingRefreshToken: 'missing-refresh-token', + ExpiredSessionTokenDecodeFailed: 'expired-session-token-decode-failed', + FetchError: 'fetch-error', + UnexpectedSDKError: 'unexpected-sdk-error', +} as const; + function assertSignInUrlExists(signInUrl: string | undefined, key: string): asserts signInUrl is string { if (!signInUrl && isDevelopmentFromSecretKey(key)) { throw new Error(`Missing signInUrl. Pass a signInUrl for dev instances if an app is satellite`); @@ -40,12 +53,6 @@ function assertSignInUrlFormatAndOrigin(_signInUrl: string, origin: string) { } } -function assertApiClient(apiClient: ApiClient | undefined): asserts apiClient is ApiClient { - if (!apiClient) { - throw new Error(`Missing apiClient. An apiClient is needed to perform token refresh.`); - } -} - /** * Currently, a request is only eligible for a handshake if we can say it's *probably* a request for a document, not a fetch or some other exotic request. * This heuristic should give us a reliable enough signal for browsers that support `Sec-Fetch-Dest` and for those that don't. @@ -103,13 +110,14 @@ export async function authenticateRequest( return updatedURL; } - function buildRedirectToHandshake() { + function buildRedirectToHandshake({ handshakeReason }: { handshakeReason: string }) { const redirectUrl = removeDevBrowserFromURL(authenticateContext.clerkUrl); const frontendApiNoProtocol = authenticateContext.frontendApi.replace(/http(s)?:\/\//, ''); const url = new URL(`https://${frontendApiNoProtocol}/v1/client/handshake`); url.searchParams.append('redirect_url', redirectUrl?.href || ''); url.searchParams.append('suffixed_cookies', authenticateContext.suffixedCookies.toString()); + url.searchParams.append(constants.QueryParameters.HandshakeReason, handshakeReason); if (authenticateContext.instanceType === 'development' && authenticateContext.devBrowserToken) { url.searchParams.append(constants.QueryParameters.DevBrowser, authenticateContext.devBrowserToken); @@ -155,7 +163,8 @@ export async function authenticateRequest( if ( authenticateContext.instanceType === 'development' && (error?.reason === TokenVerificationErrorReason.TokenExpired || - error?.reason === TokenVerificationErrorReason.TokenNotActiveYet) + error?.reason === TokenVerificationErrorReason.TokenNotActiveYet || + error?.reason === TokenVerificationErrorReason.TokenIatInTheFuture) ) { error.tokenCarrier = 'cookie'; // This probably means we're dealing with clock skew @@ -184,50 +193,119 @@ ${error.getFullMessage()}`, throw error; } - async function refreshToken(authenticateContext: AuthenticateContext): Promise { + async function refreshToken( + authenticateContext: AuthenticateContext, + ): Promise<{ data: string; error: null } | { data: null; error: any }> { // To perform a token refresh, apiClient must be defined. - assertApiClient(options.apiClient); + if (!options.apiClient) { + return { + data: null, + error: { + message: 'An apiClient is needed to perform token refresh.', + cause: { reason: RefreshTokenErrorReason.MissingApiClient }, + }, + }; + } const { sessionToken: expiredSessionToken, refreshTokenInCookie: refreshToken } = authenticateContext; - if (!expiredSessionToken || !refreshToken) { - throw new Error('Clerk: refreshTokenInCookie and sessionToken must be provided.'); + if (!expiredSessionToken) { + return { + data: null, + error: { + message: 'Session token must be provided.', + cause: { reason: RefreshTokenErrorReason.MissingSessionToken }, + }, + }; + } + if (!refreshToken) { + return { + data: null, + error: { + message: 'Refresh token must be provided.', + cause: { reason: RefreshTokenErrorReason.MissingRefreshToken }, + }, + }; } // The token refresh endpoint requires a sessionId, so we decode that from the expired token. const { data: decodeResult, errors: decodedErrors } = decodeJwt(expiredSessionToken); if (!decodeResult || decodedErrors) { - throw new Error(`Clerk: unable to decode session token.`); - } - // Perform the actual token refresh. - const tokenResponse = await options.apiClient.sessions.refreshSession(decodeResult.payload.sid, { - expired_token: expiredSessionToken || '', - refresh_token: refreshToken || '', - request_origin: authenticateContext.clerkUrl.origin, - // The refresh endpoint expects headers as Record, so we need to transform it. - request_headers: Object.fromEntries(Array.from(request.headers.entries()).map(([k, v]) => [k, [v]])), - }); + return { + data: null, + error: { + message: 'Unable to decode the expired session token.', + cause: { reason: RefreshTokenErrorReason.ExpiredSessionTokenDecodeFailed, errors: decodedErrors }, + }, + }; + } - return tokenResponse.jwt; + try { + // Perform the actual token refresh. + const tokenResponse = await options.apiClient.sessions.refreshSession(decodeResult.payload.sid, { + expired_token: expiredSessionToken || '', + refresh_token: refreshToken || '', + request_origin: authenticateContext.clerkUrl.origin, + // The refresh endpoint expects headers as Record, so we need to transform it. + request_headers: Object.fromEntries(Array.from(request.headers.entries()).map(([k, v]) => [k, [v]])), + }); + return { data: tokenResponse.jwt, error: null }; + } catch (err: any) { + if (err?.errors?.length) { + if (err.errors[0].code === 'unexpected_error') { + return { + data: null, + error: { + message: `Fetch unexpected error`, + cause: { reason: RefreshTokenErrorReason.FetchError, errors: err.errors }, + }, + }; + } + return { + data: null, + error: { + message: err.errors[0].code, + cause: { reason: err.errors[0].code, errors: err.errors }, + }, + }; + } else { + return { + data: null, + error: err, + }; + } + } } - async function attemptRefresh(authenticateContext: AuthenticateContext) { - const sessionToken = await refreshToken(authenticateContext); + async function attemptRefresh( + authenticateContext: AuthenticateContext, + ): Promise<{ data: { jwtPayload: JwtPayload; sessionToken: string }; error: null } | { data: null; error: any }> { + const { data: sessionToken, error } = await refreshToken(authenticateContext); + if (!sessionToken) { + return { data: null, error }; + } + // Since we're going to return a signedIn response, we need to decode the data from the new sessionToken. - const { data, errors } = await verifyToken(sessionToken, authenticateContext); + const { data: jwtPayload, errors } = await verifyToken(sessionToken, authenticateContext); if (errors) { - throw new Error(`Clerk: unable to verify refreshed session token.`); + return { + data: null, + error: { + message: `Clerk: unable to verify refreshed session token.`, + cause: { reason: RefreshTokenErrorReason.InvalidSessionToken, errors }, + }, + }; } - return { data, sessionToken }; + return { data: { jwtPayload, sessionToken }, error: null }; } function handleMaybeHandshakeStatus( authenticateContext: AuthenticateContext, - reason: AuthErrorReason, + reason: string, message: string, headers?: Headers, ): SignedInState | SignedOutState | HandshakeState { if (isRequestEligibleForHandshake(authenticateContext)) { // Right now the only usage of passing in different headers is for multi-domain sync, which redirects somewhere else. // In the future if we want to decorate the handshake redirect with additional headers per call we need to tweak this logic. - const handshakeHeaders = headers ?? buildRedirectToHandshake(); + const handshakeHeaders = headers ?? buildRedirectToHandshake({ handshakeReason: reason }); // Chrome aggressively caches inactive tabs. If we don't set the header here, // all 307 redirects will be cached and the handshake will end up in an infinite loop. @@ -356,9 +434,11 @@ ${error.getFullMessage()}`, constants.QueryParameters.ClerkRedirectUrl, authenticateContext.clerkUrl.toString(), ); + const authErrReason = AuthErrorReason.SatelliteCookieNeedsSyncing; + redirectURL.searchParams.append(constants.QueryParameters.HandshakeReason, authErrReason); const headers = new Headers({ [constants.Headers.Location]: redirectURL.toString() }); - return handleMaybeHandshakeStatus(authenticateContext, AuthErrorReason.SatelliteCookieNeedsSyncing, '', headers); + return handleMaybeHandshakeStatus(authenticateContext, authErrReason, '', headers); } // Multi-domain development sync flow @@ -376,9 +456,11 @@ ${error.getFullMessage()}`, ); } redirectBackToSatelliteUrl.searchParams.append(constants.QueryParameters.ClerkSynced, 'true'); + const authErrReason = AuthErrorReason.PrimaryRespondsToSyncing; + redirectBackToSatelliteUrl.searchParams.append(constants.QueryParameters.HandshakeReason, authErrReason); const headers = new Headers({ [constants.Headers.Location]: redirectBackToSatelliteUrl.toString() }); - return handleMaybeHandshakeStatus(authenticateContext, AuthErrorReason.PrimaryRespondsToSyncing, '', headers); + return handleMaybeHandshakeStatus(authenticateContext, authErrReason, '', headers); } /** * End multi-domain sync flows @@ -408,7 +490,7 @@ ${error.getFullMessage()}`, } if (decodeResult.payload.iat < authenticateContext.clientUat) { - return handleMaybeHandshakeStatus(authenticateContext, AuthErrorReason.SessionTokenOutdated, ''); + return handleMaybeHandshakeStatus(authenticateContext, AuthErrorReason.SessionTokenIATBeforeClientUAT, ''); } try { @@ -432,13 +514,29 @@ ${error.getFullMessage()}`, return signedOut(authenticateContext, AuthErrorReason.UnexpectedError); } + let refreshError: string | null; + if (isRequestEligibleForRefresh(err, authenticateContext, request)) { - try { - const refreshResponse = await attemptRefresh(authenticateContext); - return signedIn(authenticateContext, refreshResponse.data, undefined, refreshResponse.sessionToken); - } catch (error) { - // If there's any error, simply fallback to the handshake flow. - console.error('Clerk: unable to refresh token:', error); + const { data, error } = await attemptRefresh(authenticateContext); + if (data) { + return signedIn(authenticateContext, data.jwtPayload, undefined, data.sessionToken); + } + + // If there's any error, simply fallback to the handshake flow. + console.error('Clerk: unable to refresh token:', error?.message || error); + if (error?.cause?.reason) { + refreshError = error.cause.reason; + } else { + refreshError = RefreshTokenErrorReason.UnexpectedSDKError; + } + } else { + if (request.method !== 'GET') { + refreshError = RefreshTokenErrorReason.NonEligibleNonGet; + } else if (!authenticateContext.refreshTokenInCookie) { + refreshError = RefreshTokenErrorReason.NonEligibleNoCookie; + } else { + //refresh error is not applicable if token verification error is not 'session-token-expired' + refreshError = null; } } @@ -447,12 +545,13 @@ ${error.getFullMessage()}`, const reasonToHandshake = [ TokenVerificationErrorReason.TokenExpired, TokenVerificationErrorReason.TokenNotActiveYet, + TokenVerificationErrorReason.TokenIatInTheFuture, ].includes(err.reason); if (reasonToHandshake) { return handleMaybeHandshakeStatus( authenticateContext, - AuthErrorReason.SessionTokenOutdated, + convertTokenVerificationErrorReasonToAuthErrorReason({ tokenError: err.reason, refreshError }), err.getFullMessage(), ); } @@ -474,3 +573,22 @@ export const debugRequestState = (params: RequestState) => { const { isSignedIn, proxyUrl, reason, message, publishableKey, isSatellite, domain } = params; return { isSignedIn, proxyUrl, reason, message, publishableKey, isSatellite, domain }; }; + +const convertTokenVerificationErrorReasonToAuthErrorReason = ({ + tokenError, + refreshError, +}: { + tokenError: TokenVerificationErrorReason; + refreshError: string | null; +}): string => { + switch (tokenError) { + case TokenVerificationErrorReason.TokenExpired: + return `${AuthErrorReason.SessionTokenExpired}-refresh-${refreshError}`; + case TokenVerificationErrorReason.TokenNotActiveYet: + return AuthErrorReason.SessionTokenNBF; + case TokenVerificationErrorReason.TokenIatInTheFuture: + return AuthErrorReason.SessionTokenIatInTheFuture; + default: + return AuthErrorReason.UnexpectedError; + } +}; diff --git a/packages/chrome-extension/CHANGELOG.md b/packages/chrome-extension/CHANGELOG.md index 26502331dd9..6d2b973bcd9 100644 --- a/packages/chrome-extension/CHANGELOG.md +++ b/packages/chrome-extension/CHANGELOG.md @@ -1,5 +1,30 @@ # Change Log +## 1.3.10 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`d7cea3f84`](https://github.com/clerk/javascript/commit/d7cea3f8478fca0d98574456c4f38e4279ef7c9b), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/clerk-js@5.23.0 + - @clerk/clerk-react@5.9.3 + - @clerk/shared@2.8.3 + +## 1.3.9 + +### Patch Changes + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`ef824dc6a`](https://github.com/clerk/javascript/commit/ef824dc6a534ad04d3405f7fef58536dcaf01978), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529), [`cac25e0b7`](https://github.com/clerk/javascript/commit/cac25e0b7b3b8f779c565a307b0d99db621e7d36)]: + - @clerk/shared@2.8.2 + - @clerk/clerk-react@5.9.2 + - @clerk/clerk-js@5.22.4 + +## 1.3.8 + +### Patch Changes + +- Updated dependencies [[`7a298bed5`](https://github.com/clerk/javascript/commit/7a298bed566b71043ac4b8bf3cf132ef3006cf36)]: + - @clerk/clerk-js@5.22.3 + ## 1.3.7 ### Patch Changes diff --git a/packages/chrome-extension/package.json b/packages/chrome-extension/package.json index 71fee869982..e13f3e7f538 100644 --- a/packages/chrome-extension/package.json +++ b/packages/chrome-extension/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/chrome-extension", - "version": "1.3.7", + "version": "1.3.10", "description": "Clerk SDK for Chrome extensions", "keywords": [ "auth", @@ -47,9 +47,9 @@ "test:coverage": "jest --collectCoverage && open coverage/lcov-report/index.html" }, "dependencies": { - "@clerk/clerk-js": "5.22.2", - "@clerk/clerk-react": "5.9.1", - "@clerk/shared": "2.8.1", + "@clerk/clerk-js": "5.23.0", + "@clerk/clerk-react": "5.9.3", + "@clerk/shared": "2.8.3", "webextension-polyfill": "^0.10.0" }, "devDependencies": { diff --git a/packages/clerk-js/CHANGELOG.md b/packages/clerk-js/CHANGELOG.md index e6736651b4c..69fc671a3d9 100644 --- a/packages/clerk-js/CHANGELOG.md +++ b/packages/clerk-js/CHANGELOG.md @@ -1,5 +1,48 @@ # Change Log +## 5.23.0 + +### Minor Changes + +- Hide sign up url from `` component when mode is `restricted` ([#4206](https://github.com/clerk/javascript/pull/4206)) by [@nikospapcom](https://github.com/nikospapcom) + +### Patch Changes + +- Handle gracefully Coinbase Wallet initial configuration ([#4218](https://github.com/clerk/javascript/pull/4218)) by [@chanioxaris](https://github.com/chanioxaris) + +- Supports default role on `OrganizationProfile` invitations. When inviting a member, the default role will be automatically selected, otherwise it falls back to the only available role. ([#4210](https://github.com/clerk/javascript/pull/4210)) by [@LauraBeatris](https://github.com/LauraBeatris) + +- Add type for \_\_internal_country ([#4215](https://github.com/clerk/javascript/pull/4215)) by [@dstaley](https://github.com/dstaley) + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + - @clerk/localizations@3.0.6 + - @clerk/shared@2.8.3 + +## 5.22.4 + +### Patch Changes + +- Fix UserProfile and OrganizationProfile wrong padding on footer for small screens when Development notice is enabled ([#4191](https://github.com/clerk/javascript/pull/4191)) by [@octoper](https://github.com/octoper) + +- Internal change to move `iconImageUrl` util to `shared` package. ([#4188](https://github.com/clerk/javascript/pull/4188)) by [@alexcarpenter](https://github.com/alexcarpenter) + +- Only render the Sign out of all accounts action within `` when there are multiple sessions. ([#4200](https://github.com/clerk/javascript/pull/4200)) by [@alexcarpenter](https://github.com/alexcarpenter) + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529)]: + - @clerk/shared@2.8.2 + - @clerk/types@4.21.1 + - @clerk/localizations@3.0.5 + +## 5.22.3 + +### Patch Changes + +- Restore behavior of MetaMask compatible Web3 wallets. Before, even if a user didn't use the MetaMask browser extension but a compatible one, such as Rabby Wallet, it was possible to use it as they share the same API to authenticate themselves. This behavior stopped working when we added support for EIP6963 regarding handling multiple injected providers. This commit restores the previous behavior by using the existing injected provider if there is a single one ([#4185](https://github.com/clerk/javascript/pull/4185)) by [@chanioxaris](https://github.com/chanioxaris) + +- Updated dependencies [[`5dde18f6b`](https://github.com/clerk/javascript/commit/5dde18f6b55ed4d5c2a6a5246ee3b3ba0d077df3)]: + - @clerk/localizations@3.0.4 + ## 5.22.2 ### Patch Changes diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index f0f2208f716..6e8b142720e 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -1,6 +1,6 @@ { "files": [ - { "path": "./dist/clerk.browser.js", "maxSize": "64.1kB" }, + { "path": "./dist/clerk.browser.js", "maxSize": "65kB" }, { "path": "./dist/clerk.headless.js", "maxSize": "43kB" }, { "path": "./dist/ui-common*.js", "maxSize": "86KB" }, { "path": "./dist/vendors*.js", "maxSize": "70KB" }, diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index 7c0edc2f866..97f6ef9c191 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/clerk-js", - "version": "5.22.2", + "version": "5.23.0", "description": "Clerk JS library", "keywords": [ "clerk", @@ -50,9 +50,9 @@ }, "browserslist": "last 2 versions, ios_saf > 12, Safari > 12, > 1%, not dead, not ie > 0", "dependencies": { - "@clerk/localizations": "3.0.3", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/localizations": "3.0.6", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "@coinbase/wallet-sdk": "4.0.4", "@emotion/cache": "11.11.0", "@emotion/react": "11.11.1", diff --git a/packages/clerk-js/src/core/constants.ts b/packages/clerk-js/src/core/constants.ts index 82a8c3f3207..a025cfc8ce9 100644 --- a/packages/clerk-js/src/core/constants.ts +++ b/packages/clerk-js/src/core/constants.ts @@ -1,3 +1,5 @@ +import type { SignUpModes } from '@clerk/types'; + // TODO: Do we still have a use for this or can we simply preserve all params? export const PRESERVED_QUERYSTRING_PARAMS = [ 'redirect_url', @@ -29,3 +31,8 @@ export const SIGN_IN_INITIAL_VALUE_KEYS = ['email_address', 'phone_number', 'use export const SIGN_UP_INITIAL_VALUE_KEYS = ['email_address', 'phone_number', 'username', 'first_name', 'last_name']; export const DEBOUNCE_MS = 350; + +export const SIGN_UP_MODES: Record = { + PUBLIC: 'public', + RESTRICTED: 'restricted', +}; diff --git a/packages/clerk-js/src/core/resources/OrganizationSettings.ts b/packages/clerk-js/src/core/resources/OrganizationSettings.ts index e7c0fbb4072..c6f99520074 100644 --- a/packages/clerk-js/src/core/resources/OrganizationSettings.ts +++ b/packages/clerk-js/src/core/resources/OrganizationSettings.ts @@ -11,6 +11,7 @@ export class OrganizationSettings extends BaseResource implements OrganizationSe domains!: { enabled: boolean; enrollmentModes: OrganizationEnrollmentMode[]; + defaultRole: string | null; }; public constructor(data: OrganizationSettingsJSON) { @@ -26,6 +27,7 @@ export class OrganizationSettings extends BaseResource implements OrganizationSe this.domains = { enabled: domains?.enabled || false, enrollmentModes: domains?.enrollment_modes || [], + defaultRole: domains?.default_role || null, }; return this; } diff --git a/packages/clerk-js/src/core/resources/SamlAccount.ts b/packages/clerk-js/src/core/resources/SamlAccount.ts index 2b7988c20ea..8a01897d3bb 100644 --- a/packages/clerk-js/src/core/resources/SamlAccount.ts +++ b/packages/clerk-js/src/core/resources/SamlAccount.ts @@ -1,5 +1,13 @@ -import type { SamlAccountJSON, SamlAccountResource, SamlIdpSlug, VerificationResource } from '@clerk/types'; +import type { + SamlAccountConnectionJSON, + SamlAccountConnectionResource, + SamlAccountJSON, + SamlAccountResource, + SamlIdpSlug, + VerificationResource, +} from '@clerk/types'; +import { unixEpochToDate } from '../../utils/date'; import { BaseResource } from './Base'; import { Verification } from './Verification'; @@ -12,6 +20,7 @@ export class SamlAccount extends BaseResource implements SamlAccountResource { firstName = ''; lastName = ''; verification: VerificationResource | null = null; + samlConnection: SamlAccountConnectionResource | null = null; public constructor(data: Partial, pathRoot: string); public constructor(data: SamlAccountJSON, pathRoot: string) { @@ -37,6 +46,46 @@ export class SamlAccount extends BaseResource implements SamlAccountResource { this.verification = new Verification(data.verification); } + if (data.saml_connection) { + this.samlConnection = new SamlAccountConnection(data.saml_connection); + } + + return this; + } +} + +export class SamlAccountConnection extends BaseResource implements SamlAccountConnectionResource { + id!: string; + name!: string; + domain!: string; + active!: boolean; + provider!: string; + syncUserAttributes!: boolean; + allowSubdomains!: boolean; + allowIdpInitiated!: boolean; + disableAdditionalIdentifications!: boolean; + createdAt!: Date; + updatedAt!: Date; + + constructor(data: SamlAccountConnectionJSON | null) { + super(); + this.fromJSON(data); + } + protected fromJSON(data: SamlAccountConnectionJSON | null): this { + if (data) { + this.id = data.id; + this.name = data.name; + this.domain = data.domain; + this.active = data.active; + this.provider = data.provider; + this.syncUserAttributes = data.sync_user_attributes; + this.allowSubdomains = data.allow_subdomains; + this.allowIdpInitiated = data.allow_idp_initiated; + this.disableAdditionalIdentifications = data.disable_additional_identifications; + this.createdAt = unixEpochToDate(data.created_at); + this.updatedAt = unixEpochToDate(data.updated_at); + } + return this; } } diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index 1700a4aa9f7..a2ab6278140 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -207,7 +207,24 @@ export class SignUp extends BaseResource implements SignUpResource { clerkVerifyWeb3WalletCalledBeforeCreate('SignUp'); } - const signature = await generateSignature({ identifier, nonce, provider }); + let signature: string; + try { + signature = await generateSignature({ identifier, nonce, provider }); + } catch (err) { + // There is a chance that as a first time visitor when you try to setup and use the + // Coinbase Wallet from scratch in order to authenticate, the initial generate + // signature request to be rejected. For this reason we retry the request once more + // in order for the flow to be able to be completed successfully. + // + // error code 4001 means the user rejected the request + // Reference: https://docs.cdp.coinbase.com/wallet-sdk/docs/errors + if (provider === 'coinbase_wallet' && err.code === 4001) { + signature = await generateSignature({ identifier, nonce, provider }); + } else { + throw err; + } + } + return this.attemptWeb3WalletVerification({ signature, strategy }); }; diff --git a/packages/clerk-js/src/ui/common/constants.ts b/packages/clerk-js/src/ui/common/constants.ts index 535c3f3e1c5..792a541203d 100644 --- a/packages/clerk-js/src/ui/common/constants.ts +++ b/packages/clerk-js/src/ui/common/constants.ts @@ -101,11 +101,3 @@ export const WEB3_PROVIDERS: Web3Providers = Object.freeze({ export function getWeb3ProviderData(name: Web3Provider): Web3ProviderData | undefined | null { return WEB3_PROVIDERS[name]; } - -/** - * Returns the URL for a static SVG image - * using the new img.clerk.com service - */ -export function iconImageUrl(id: string): string { - return `https://img.clerk.com/static/${id}.svg`; -} diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/InviteMembersForm.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/InviteMembersForm.tsx index 1a8a6beb99b..56a54ddd274 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/InviteMembersForm.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/InviteMembersForm.tsx @@ -4,6 +4,7 @@ import type { ClerkAPIError } from '@clerk/types'; import type { FormEvent } from 'react'; import { useState } from 'react'; +import { useEnvironment } from '../../contexts'; import { Flex } from '../../customizables'; import { Form, FormButtonContainer, TagInput, useCardState } from '../../elements'; import { useFetchRoles } from '../../hooks/useFetchRoles'; @@ -187,6 +188,8 @@ const AsyncRoleSelect = (field: ReturnType>) => { const { t } = useLocalizations(); + const defaultRole = useDefaultRole(); + return ( >) => { > field.setValue(value)} @@ -206,3 +210,20 @@ const AsyncRoleSelect = (field: ReturnType>) => { ); }; + +/** + * Determines default role from the organization settings or fallback to + * the only available role. + */ +const useDefaultRole = () => { + const { options } = useFetchRoles(); + const { organizationSettings } = useEnvironment(); + + let defaultRole = organizationSettings.domains.defaultRole; + + if (!defaultRole && options?.length === 1) { + defaultRole = options[0].value; + } + + return defaultRole; +}; diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx index 5479b8afd4c..4afb1760220 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/__tests__/InviteMembersPage.test.tsx @@ -1,6 +1,7 @@ import type { OrganizationInvitationResource } from '@clerk/types'; import { describe } from '@jest/globals'; import { waitFor } from '@testing-library/dom'; +import React from 'react'; import { ClerkAPIResponseError } from '../../../../core/resources'; import { render } from '../../../../testUtils'; @@ -41,7 +42,156 @@ describe('InviteMembersPage', () => { getByText('Enter or paste one or more email addresses, separated by spaces or commas.'); }); - describe('Submitting', () => { + describe('with default role', () => { + it("initializes with the organization's default role", async () => { + const defaultRole = 'mydefaultrole'; + + const { wrapper, fixtures } = await createFixtures(f => { + f.withOrganizations(); + f.withOrganizationDomains(undefined, defaultRole); + f.withUser({ + email_addresses: ['test@clerk.com'], + organization_memberships: [{ name: 'Org1', role: 'admin' }], + }); + }); + + fixtures.clerk.organization?.getRoles.mockResolvedValue({ + total_count: 2, + data: [ + { + pathRoot: '', + reload: jest.fn(), + id: 'member', + key: 'member', + name: 'member', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, + { + pathRoot: '', + reload: jest.fn(), + id: 'admin', + key: 'admin', + name: 'Admin', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, + { + pathRoot: '', + reload: jest.fn(), + id: defaultRole, + key: defaultRole, + name: defaultRole, + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, + ], + }); + + fixtures.clerk.organization?.inviteMembers.mockResolvedValueOnce([{}] as OrganizationInvitationResource[]); + const { getByRole, userEvent, getByTestId } = render( + + + , + { wrapper }, + ); + await userEvent.type(getByTestId('tag-input'), 'test+1@clerk.com,'); + await userEvent.click(getByRole('button', { name: /mydefaultrole/i })); + }); + + it("initializes if there's only one role available", async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withOrganizations(); + f.withUser({ + email_addresses: ['test@clerk.com'], + organization_memberships: [{ name: 'Org1', role: 'admin' }], + }); + }); + + fixtures.clerk.organization?.getRoles.mockResolvedValue({ + total_count: 1, + data: [ + { + pathRoot: '', + reload: jest.fn(), + id: 'member', + key: 'member', + name: 'member', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, + ], + }); + + fixtures.clerk.organization?.inviteMembers.mockResolvedValueOnce([{}] as OrganizationInvitationResource[]); + const { getByRole, userEvent, getByTestId } = render( + + + , + { wrapper }, + ); + await userEvent.type(getByTestId('tag-input'), 'test+1@clerk.com,'); + await waitFor(() => expect(getByRole('button', { name: /member/i })).toBeInTheDocument()); + }); + + it("does not initialize if there's neither a default role nor a unique role", async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withOrganizations(); + f.withUser({ + email_addresses: ['test@clerk.com'], + organization_memberships: [{ name: 'Org1', role: 'admin' }], + }); + }); + + fixtures.clerk.organization?.getRoles.mockResolvedValue({ + total_count: 1, + data: [ + { + pathRoot: '', + reload: jest.fn(), + id: 'member', + key: 'member', + name: 'member', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, + { + pathRoot: '', + reload: jest.fn(), + id: 'admin', + key: 'admin', + name: 'admin', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, + ], + }); + + fixtures.clerk.organization?.inviteMembers.mockResolvedValueOnce([{}] as OrganizationInvitationResource[]); + const { getByRole, userEvent, getByTestId } = render( + + + , + { wrapper }, + ); + await userEvent.type(getByTestId('tag-input'), 'test+1@clerk.com,'); + await waitFor(() => expect(getByRole('button', { name: /select role/i })).toBeInTheDocument()); + }); + }); + + describe('when submitting', () => { it('keeps the Send button disabled until a role is selected and one or more email has been entered', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withOrganizations(); @@ -65,6 +215,17 @@ describe('InviteMembersPage', () => { createdAt: new Date(), updatedAt: new Date(), }, + { + pathRoot: '', + reload: jest.fn(), + id: 'admin', + key: 'admin', + name: 'Admin', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, ], }); @@ -108,6 +269,17 @@ describe('InviteMembersPage', () => { createdAt: new Date(), updatedAt: new Date(), }, + { + pathRoot: '', + reload: jest.fn(), + id: 'admin', + key: 'admin', + name: 'Admin', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, ], }); @@ -154,6 +326,17 @@ describe('InviteMembersPage', () => { createdAt: new Date(), updatedAt: new Date(), }, + { + pathRoot: '', + reload: jest.fn(), + id: 'admin', + key: 'admin', + name: 'Admin', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, ], }); @@ -259,6 +442,17 @@ describe('InviteMembersPage', () => { createdAt: new Date(), updatedAt: new Date(), }, + { + pathRoot: '', + reload: jest.fn(), + id: 'admin', + key: 'admin', + name: 'Admin', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, ], }); @@ -318,6 +512,17 @@ describe('InviteMembersPage', () => { createdAt: new Date(), updatedAt: new Date(), }, + { + pathRoot: '', + reload: jest.fn(), + id: 'admin', + key: 'admin', + name: 'Admin', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, ], }); @@ -373,6 +578,17 @@ describe('InviteMembersPage', () => { createdAt: new Date(), updatedAt: new Date(), }, + { + pathRoot: '', + reload: jest.fn(), + id: 'admin', + key: 'admin', + name: 'Admin', + description: '', + permissions: [], + createdAt: new Date(), + updatedAt: new Date(), + }, ], }); diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx index d2b6f838356..4bfb6c6dd57 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx @@ -3,7 +3,7 @@ import { isWebAuthnAutofillSupported, isWebAuthnSupported } from '@clerk/shared/ import type { ClerkAPIError, SignInCreateParams, SignInResource } from '@clerk/types'; import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; -import { ERROR_CODES } from '../../../core/constants'; +import { ERROR_CODES, SIGN_UP_MODES } from '../../../core/constants'; import { clerkInvalidFAPIResponse } from '../../../core/errors'; import { getClerkQueryParam, removeClerkQueryParam } from '../../../utils'; import type { SignInStartIdentifier } from '../../common'; @@ -410,13 +410,15 @@ export function _SignInStart(): JSX.Element { - - - - + {userSettings.signUp.mode === SIGN_UP_MODES.PUBLIC && ( + + + + + )} diff --git a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx index 8ee9c1e1cea..b88ad62ae38 100644 --- a/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/__tests__/SignInStart.test.tsx @@ -111,6 +111,29 @@ describe('SignInStart', () => { }); }); + describe('Restricted mode', () => { + it('"Don\'t have an account?" text should not be presented', async () => { + const { wrapper } = await createFixtures(f => { + f.withEmailAddress(); + f.withRestrictedMode(); + }); + render(, { wrapper }); + expect(screen.queryByText(/Don’t have an account/i)).not.toBeInTheDocument(); + }); + + it('"Don\'t have an account?" text should be visible', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withEmailAddress(); + }); + render(, { wrapper }); + + const signUpLink = screen.getByText(/Don’t have an account/i).nextElementSibling; + expect(signUpLink?.textContent).toBe('Sign up'); + expect(signUpLink?.tagName.toUpperCase()).toBe('A'); + expect(signUpLink?.getAttribute('href')).toMatch(fixtures.environment.displayConfig.signUpUrl); + }); + }); + describe('Social OAuth', () => { it.each(OAUTH_PROVIDERS)('shows the "Continue with $name" social OAuth button', async ({ provider, name }) => { const { wrapper } = await createFixtures(f => { diff --git a/packages/clerk-js/src/ui/components/UserButton/UserButtonPopover.tsx b/packages/clerk-js/src/ui/components/UserButton/UserButtonPopover.tsx index 10b737e30b9..ce5ce45d6b0 100644 --- a/packages/clerk-js/src/ui/components/UserButton/UserButtonPopover.tsx +++ b/packages/clerk-js/src/ui/components/UserButton/UserButtonPopover.tsx @@ -66,7 +66,9 @@ export const UserButtonPopover = React.forwardRef - {!authConfig.singleSessionMode && } + {!authConfig.singleSessionMode && otherSessions.length > 0 && ( + + )} diff --git a/packages/clerk-js/src/ui/components/UserProfile/AccountPage.tsx b/packages/clerk-js/src/ui/components/UserProfile/AccountPage.tsx index 4380c7b821b..a4673fa9af4 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/AccountPage.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/AccountPage.tsx @@ -15,6 +15,7 @@ export const AccountPage = withCardStateProvider(() => { const { attributes, saml, social } = useEnvironment().userSettings; const card = useCardState(); const { user } = useUser(); + const showUsername = attributes.username.enabled; const showEmail = attributes.email_address.enabled; const showPhone = attributes.phone_number.enabled; @@ -22,6 +23,12 @@ export const AccountPage = withCardStateProvider(() => { const showSamlAccounts = saml && saml.enabled && user && user.samlAccounts.length > 0; const showWeb3 = attributes.web3_wallet.enabled; + const shouldAllowIdentificationCreation = + !showSamlAccounts || + !user?.samlAccounts?.some( + samlAccount => samlAccount.active && samlAccount.samlConnection?.disableAdditionalIdentifications, + ); + return ( { {showUsername && } - {showEmail && } - {showPhone && } - {showConnectedAccounts && } + {showEmail && } + {showPhone && } + {showConnectedAccounts && } - {/*TODO-STEP-UP: DO these 2*/} + {/*TODO-STEP-UP: Verify that these work as expected*/} {showSamlAccounts && } - {showWeb3 && } + {showWeb3 && } ); diff --git a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx index 571c0d31737..fa31a77fa37 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ConnectedAccountsSection.tsx @@ -47,41 +47,43 @@ const errorCodesForReconnect = [ 'external_account_email_address_verification_required', ]; -export const ConnectedAccountsSection = withCardStateProvider(() => { - const { user } = useUser(); - const card = useCardState(); +export const ConnectedAccountsSection = withCardStateProvider( + ({ shouldAllowCreation = true }: { shouldAllowCreation?: boolean }) => { + const { user } = useUser(); + const card = useCardState(); + const hasExternalAccounts = Boolean(user?.externalAccounts?.length); - if (!user) { - return null; - } + if (!user || (!shouldAllowCreation && !hasExternalAccounts)) { + return null; + } - const accounts = [ - ...user.verifiedExternalAccounts, - ...user.unverifiedExternalAccounts.filter(a => a.verification?.error), - ]; + const accounts = [ + ...user.verifiedExternalAccounts, + ...user.unverifiedExternalAccounts.filter(a => a.verification?.error), + ]; - return ( - - {card.error} - - - {accounts.map(account => ( - - ))} - - - - - - ); -}); + return ( + + {card.error} + + + {accounts.map(account => ( + + ))} + + {shouldAllowCreation && } + + + ); + }, +); const ConnectedAccount = ({ account }: { account: ExternalAccountResource }) => { const { additionalOAuthScopes, componentName, mode } = useUserProfileContext(); diff --git a/packages/clerk-js/src/ui/components/UserProfile/EmailsSection.tsx b/packages/clerk-js/src/ui/components/UserProfile/EmailsSection.tsx index e7d643775a7..a59bc7f3d4f 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/EmailsSection.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/EmailsSection.tsx @@ -35,7 +35,7 @@ const EmailScreen = (props: EmailScreenProps) => { ); }; -export const EmailsSection = () => { +export const EmailsSection = ({ shouldAllowCreation = true }) => { const { user } = useUser(); return ( @@ -79,19 +79,21 @@ export const EmailsSection = () => { ))} - - - - - - - - - - + {shouldAllowCreation && ( + <> + + + + + + + + + + )} diff --git a/packages/clerk-js/src/ui/components/UserProfile/PhoneSection.tsx b/packages/clerk-js/src/ui/components/UserProfile/PhoneSection.tsx index 6023c4834c0..ea98b20f3a4 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/PhoneSection.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/PhoneSection.tsx @@ -35,8 +35,13 @@ const PhoneScreen = (props: PhoneScreenProps) => { ); }; -export const PhoneSection = () => { +export const PhoneSection = ({ shouldAllowCreation = true }: { shouldAllowCreation?: boolean }) => { const { user } = useUser(); + const hasPhoneNumbers = Boolean(user?.phoneNumbers?.length); + + if (!shouldAllowCreation && !hasPhoneNumbers) { + return null; + } return ( { ))} - - - - - - - - - - + {shouldAllowCreation && ( + <> + + + + + + + + + + )} diff --git a/packages/clerk-js/src/ui/components/UserProfile/Web3Section.tsx b/packages/clerk-js/src/ui/components/UserProfile/Web3Section.tsx index cd1444a94f1..492bef55c29 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/Web3Section.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/Web3Section.tsx @@ -28,75 +28,82 @@ const shortenWeb3Address = (address: string) => { return address.slice(0, 6) + '...' + address.slice(-4); }; -export const Web3Section = withCardStateProvider(() => { - const { user } = useUser(); - const card = useCardState(); - const { strategyToDisplayData } = useEnabledThirdPartyProviders(); +export const Web3Section = withCardStateProvider( + ({ shouldAllowCreation = true }: { shouldAllowCreation?: boolean }) => { + const { user } = useUser(); + const card = useCardState(); + const { strategyToDisplayData } = useEnabledThirdPartyProviders(); + const hasWeb3Wallets = Boolean(user?.web3Wallets?.length); - return ( - - {card.error} - - - {user?.web3Wallets.map(wallet => { - const strategy = wallet.verification.strategy as keyof typeof strategyToDisplayData; + if (!shouldAllowCreation && !hasWeb3Wallets) { + return null; + } - return ( - strategyToDisplayData[strategy] && ( - - - - ({ alignItems: 'center', gap: t.space.$2, width: '100%' })}> - {strategyToDisplayData[strategy].iconUrl && ( - {strategyToDisplayData[strategy].name} ({ width: theme.sizes.$4 })} - /> - )} - - - - {strategyToDisplayData[strategy].name} ({shortenWeb3Address(wallet.web3Wallet)}) - - {user?.primaryWeb3WalletId === wallet.id && ( - - )} - {wallet.verification.status !== 'verified' && ( - - )} - - - - - - + return ( + + {card.error} + + + {user?.web3Wallets.map(wallet => { + const strategy = wallet.verification.strategy as keyof typeof strategyToDisplayData; - - - - - - - ) - ); - })} - - - - - ); -}); + return ( + strategyToDisplayData[strategy] && ( + + + + ({ alignItems: 'center', gap: t.space.$2, width: '100%' })}> + {strategyToDisplayData[strategy].iconUrl && ( + {strategyToDisplayData[strategy].name} ({ width: theme.sizes.$4 })} + /> + )} + + + + {strategyToDisplayData[strategy].name} ({shortenWeb3Address(wallet.web3Wallet)}) + + {user?.primaryWeb3WalletId === wallet.id && ( + + )} + {wallet.verification.status !== 'verified' && ( + + )} + + + + + + + + + + + + + + ) + ); + })} + + {shouldAllowCreation && } + + + ); + }, +); const Web3WalletMenu = () => { const { open } = useActionContext(); diff --git a/packages/clerk-js/src/ui/components/UserProfile/__tests__/AccountPage.test.tsx b/packages/clerk-js/src/ui/components/UserProfile/__tests__/AccountPage.test.tsx index 4435d68f8ae..65e9e3e1e9f 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/__tests__/AccountPage.test.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/__tests__/AccountPage.test.tsx @@ -1,3 +1,4 @@ +import type { SamlAccountJSON } from '@clerk/types'; import { describe, it } from '@jest/globals'; import { render, screen, waitFor } from '../../../../testUtils'; @@ -110,5 +111,90 @@ describe('AccountPage', () => { screen.getByText(/Enterprise Accounts/i); screen.getByText(/Okta Workforce/i); }); + + describe('with `disable_additional_identifications`', () => { + const emailAddress = 'george@jungle.com'; + const phoneNumber = '+301234567890'; + const firstName = 'George'; + const lastName = 'Clerk'; + + const samlAccount: SamlAccountJSON = { + id: 'samlacc_foo', + provider: 'saml_okta', + email_address: emailAddress, + first_name: firstName, + last_name: lastName, + saml_connection: { + id: 'samlc_foo', + active: true, + disable_additional_identifications: true, + allow_idp_initiated: true, + allow_subdomains: true, + domain: 'foo.com', + name: 'Foo', + created_at: new Date().getTime(), + updated_at: new Date().getTime(), + object: 'saml_connection', + provider: 'saml_okta', + sync_user_attributes: true, + }, + active: true, + object: 'saml_account', + provider_user_id: '', + }; + + it('shows only the enterprise accounts of the user', async () => { + const { wrapper } = await createFixtures(f => { + f.withEmailAddress(); + f.withPhoneNumber(); + f.withSocialProvider({ provider: 'google' }); + f.withSaml(); + f.withUser({ + email_addresses: [emailAddress], + saml_accounts: [samlAccount], + first_name: firstName, + last_name: lastName, + }); + }); + + render(, { wrapper }); + + expect(screen.queryByText(/Add email address/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/Phone numbers/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/Connected Accounts/i)).not.toBeInTheDocument(); + screen.getByText(/Enterprise Accounts/i); + screen.getByText(/Okta Workforce/i); + }); + + it('shows the enterprise accounts of the user, and the other sections, but hides the add button', async () => { + const { wrapper } = await createFixtures(f => { + f.withEmailAddress(); + f.withPhoneNumber(); + f.withSocialProvider({ provider: 'google' }); + f.withSaml(); + f.withUser({ + email_addresses: [emailAddress], + phone_numbers: [phoneNumber], + external_accounts: [{ provider: 'google', email_address: 'test@clerk.com' }], + saml_accounts: [samlAccount], + first_name: firstName, + last_name: lastName, + }); + }); + + render(, { wrapper }); + + screen.getByText(/Email addresses/i); + screen.getByText(/Phone numbers/i); + screen.getByText(/Connected Accounts/i); + screen.getByText(/Enterprise Accounts/i); + screen.getByText(/Okta Workforce/i); + + // Add buttons should be hidden + expect(screen.queryByText(/Add email address/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/Add phone number/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/Connect account/i)).not.toBeInTheDocument(); + }); + }); }); }); diff --git a/packages/clerk-js/src/ui/elements/Card/CardFooter.tsx b/packages/clerk-js/src/ui/elements/Card/CardFooter.tsx index 23ceb1cf69a..007f851d43d 100644 --- a/packages/clerk-js/src/ui/elements/Card/CardFooter.tsx +++ b/packages/clerk-js/src/ui/elements/Card/CardFooter.tsx @@ -37,7 +37,7 @@ export const CardFooter = React.forwardRef((pro }); const profileCardFooterStyles = (t: InternalTheme) => ({ - padding: `${t.space.$4} ${t.space.$6} ${t.space.$2}`, + padding: `${t.space.$4} ${t.space.$8}`, }); return ( @@ -59,7 +59,7 @@ export const CardFooter = React.forwardRef((pro marginTop: 0, }, }), - isProfileFooter ? profileCardFooterStyles : footerStyles, + !isProfileFooter && footerStyles, sx, ]} {...rest} @@ -72,6 +72,7 @@ export const CardFooter = React.forwardRef((pro devModeNoticeSx={t => ({ padding: t.space.$none, })} + outerSx={isProfileFooter ? profileCardFooterStyles : undefined} withDevOverlay /> diff --git a/packages/clerk-js/src/ui/elements/IdentityPreview.tsx b/packages/clerk-js/src/ui/elements/IdentityPreview.tsx index 7b8120b4866..126a2b3a80b 100644 --- a/packages/clerk-js/src/ui/elements/IdentityPreview.tsx +++ b/packages/clerk-js/src/ui/elements/IdentityPreview.tsx @@ -1,3 +1,4 @@ +import { iconImageUrl } from '@clerk/shared/constants'; import React from 'react'; import { Button, descriptors, Flex, Icon, Text } from '../customizables'; @@ -12,7 +13,7 @@ type IdentityPreviewProps = { } & PropsOfComponent; export const IdentityPreview = (props: IdentityPreviewProps) => { - const { avatarUrl = 'https://img.clerk.com/static/avatar_placeholder.jpeg', identifier, onClick, ...rest } = props; + const { avatarUrl = iconImageUrl('avatar_placeholder', 'jpeg'), identifier, onClick, ...rest } = props; const refs = React.useRef({ avatarUrl, identifier: formatSafeIdentifier(identifier) }); const edit = onClick && ( diff --git a/packages/clerk-js/src/ui/elements/PhoneInput/PhoneInput.tsx b/packages/clerk-js/src/ui/elements/PhoneInput/PhoneInput.tsx index 3f3d6b175cb..5dc81fee5b0 100644 --- a/packages/clerk-js/src/ui/elements/PhoneInput/PhoneInput.tsx +++ b/packages/clerk-js/src/ui/elements/PhoneInput/PhoneInput.tsx @@ -246,7 +246,6 @@ const CountryCodeListItem = memo((props: CountryCodeListItemProps) => { export const PhoneInput = forwardRef( (props, ref) => { - // @ts-expect-error const { __internal_country } = useClerk(); return ( diff --git a/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx b/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx index 753a23d0454..3734d547327 100644 --- a/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx +++ b/packages/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx @@ -1,8 +1,8 @@ +import { iconImageUrl } from '@clerk/shared/constants'; import type { OAuthProvider, OAuthStrategy, Web3Provider, Web3Strategy } from '@clerk/types'; // TODO: This import shouldn't be part of @clerk/types import { OAUTH_PROVIDERS, WEB3_PROVIDERS } from '@clerk/types'; -import { iconImageUrl } from '../common/constants'; import { useEnvironment } from '../contexts/EnvironmentContext'; import { fromEntries } from '../utils'; diff --git a/packages/clerk-js/src/ui/hooks/useSaml.ts b/packages/clerk-js/src/ui/hooks/useSaml.ts index 5e350b9b233..b5fb5aac1c0 100644 --- a/packages/clerk-js/src/ui/hooks/useSaml.ts +++ b/packages/clerk-js/src/ui/hooks/useSaml.ts @@ -1,8 +1,7 @@ +import { iconImageUrl } from '@clerk/shared/constants'; import type { SamlIdpSlug } from '@clerk/types'; import { SAML_IDPS } from '@clerk/types'; -import { iconImageUrl } from '../common/constants'; - function getSamlProviderLogoUrl(provider: SamlIdpSlug = 'saml_custom'): string { return iconImageUrl(SAML_IDPS[provider]?.logo); } diff --git a/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts b/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts index 54bf863a191..7f09b4093b8 100644 --- a/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts +++ b/packages/clerk-js/src/ui/utils/test/fixtureHelpers.ts @@ -17,6 +17,7 @@ import type { VerificationJSON, } from '@clerk/types'; +import { SIGN_UP_MODES } from '../../../core/constants'; import type { OrgParams } from '../../../core/test/fixtures'; import { createUser, getOrganizationId } from '../../../core/test/fixtures'; import { createUserFixture } from './fixtures'; @@ -296,9 +297,10 @@ const createOrganizationSettingsFixtureHelpers = (environment: EnvironmentJSON) os.max_allowed_memberships = max; }; - const withOrganizationDomains = (modes?: OrganizationEnrollmentMode[]) => { + const withOrganizationDomains = (modes?: OrganizationEnrollmentMode[], defaultRole?: string) => { os.domains.enabled = true; os.domains.enrollment_modes = modes || ['automatic_invitation', 'automatic_invitation', 'manual_invitation']; + os.domains.default_role = defaultRole ?? null; }; return { withOrganizations, withMaxAllowedMemberships, withOrganizationDomains }; }; @@ -317,6 +319,8 @@ const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => { show_zxcvbn: false, min_zxcvbn_strength: 0, }; + us.sign_up.mode = SIGN_UP_MODES.PUBLIC; + const emptyAttribute = { first_factors: [], second_factors: [], @@ -476,6 +480,10 @@ const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => { }; }; + const withRestrictedMode = () => { + us.sign_up.mode = SIGN_UP_MODES.RESTRICTED; + }; + // TODO: Add the rest, consult pkg/generate/auth_config.go return { @@ -493,5 +501,6 @@ const createUserSettingsFixtureHelpers = (environment: EnvironmentJSON) => { withAuthenticatorApp, withPasskey, withPasskeySettings, + withRestrictedMode, }; }; diff --git a/packages/elements/CHANGELOG.md b/packages/elements/CHANGELOG.md index 43ed39bf3c6..a5f4d5aa3e0 100644 --- a/packages/elements/CHANGELOG.md +++ b/packages/elements/CHANGELOG.md @@ -1,5 +1,21 @@ # @clerk/elements +## 0.15.6 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + +## 0.15.5 + +### Patch Changes + +- Internal change to move `iconImageUrl` util to `shared` package. ([#4188](https://github.com/clerk/javascript/pull/4188)) by [@alexcarpenter](https://github.com/alexcarpenter) + +- Updated dependencies [[`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7)]: + - @clerk/types@4.21.1 + ## 0.15.4 ## 0.15.3 diff --git a/packages/elements/package.json b/packages/elements/package.json index 8fbf9d6cae1..ebb248b2952 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/elements", - "version": "0.15.4", + "version": "0.15.6", "description": "Clerk Elements", "keywords": [ "clerk", @@ -71,7 +71,7 @@ "test:cache:clear": "jest --clearCache --useStderr" }, "dependencies": { - "@clerk/types": "^4.21.0", + "@clerk/types": "^4.22.0", "@radix-ui/react-form": "^0.1.0", "@radix-ui/react-slot": "^1.1.0", "@xstate/react": "^4.1.1", @@ -79,9 +79,9 @@ "xstate": "^5.15.0" }, "devDependencies": { - "@clerk/clerk-react": "5.9.1", + "@clerk/clerk-react": "5.9.3", "@clerk/eslint-config-custom": "*", - "@clerk/shared": "2.8.1", + "@clerk/shared": "2.8.3", "@statelyai/inspect": "^0.4.0", "@types/node": "^18.19.33", "@types/react": "*", diff --git a/packages/elements/src/utils/clerk-js.ts b/packages/elements/src/utils/clerk-js.ts index 43cf61317a4..010067aad1d 100644 --- a/packages/elements/src/utils/clerk-js.ts +++ b/packages/elements/src/utils/clerk-js.ts @@ -5,13 +5,3 @@ export const fromEntries = (iterable: Iterable) => { return obj; }, {}); }; - -/** - * Returns the URL for a static SVG image - * using the new img.clerk.com service - * - * Pulled from `clerk-js/src/ui/common/constants.ts` - */ -export function iconImageUrl(id: string): string { - return `https://img.clerk.com/static/${id}.svg`; -} diff --git a/packages/elements/src/utils/third-party-strategies.ts b/packages/elements/src/utils/third-party-strategies.ts index 0653e031374..c46bd33cf50 100644 --- a/packages/elements/src/utils/third-party-strategies.ts +++ b/packages/elements/src/utils/third-party-strategies.ts @@ -1,5 +1,6 @@ // c.f. vendor/clerk-js/src/ui/hooks/useEnabledThirdPartyProviders.tsx [Modified] +import { iconImageUrl } from '@clerk/shared/constants'; import type { EnvironmentResource, OAuthProvider, @@ -10,7 +11,7 @@ import type { } from '@clerk/types'; import { OAUTH_PROVIDERS, WEB3_PROVIDERS } from '@clerk/types'; // TODO: This import shouldn't be part of @clerk/types -import { fromEntries, iconImageUrl } from './clerk-js'; +import { fromEntries } from './clerk-js'; export type ThirdPartyStrategy = | { diff --git a/packages/expo/CHANGELOG.md b/packages/expo/CHANGELOG.md index a5ab622e853..29ec0d9d85c 100644 --- a/packages/expo/CHANGELOG.md +++ b/packages/expo/CHANGELOG.md @@ -1,5 +1,34 @@ # Change Log +## 2.2.16 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`d7cea3f84`](https://github.com/clerk/javascript/commit/d7cea3f8478fca0d98574456c4f38e4279ef7c9b), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/clerk-js@5.23.0 + - @clerk/types@4.22.0 + - @clerk/clerk-react@5.9.3 + - @clerk/shared@2.8.3 + +## 2.2.15 + +### Patch Changes + +- Improve JSDoc comments for some public API properties ([#4190](https://github.com/clerk/javascript/pull/4190)) by [@LekoArts](https://github.com/LekoArts) + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`ef824dc6a`](https://github.com/clerk/javascript/commit/ef824dc6a534ad04d3405f7fef58536dcaf01978), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529), [`cac25e0b7`](https://github.com/clerk/javascript/commit/cac25e0b7b3b8f779c565a307b0d99db621e7d36)]: + - @clerk/shared@2.8.2 + - @clerk/clerk-react@5.9.2 + - @clerk/types@4.21.1 + - @clerk/clerk-js@5.22.4 + +## 2.2.14 + +### Patch Changes + +- Updated dependencies [[`7a298bed5`](https://github.com/clerk/javascript/commit/7a298bed566b71043ac4b8bf3cf132ef3006cf36)]: + - @clerk/clerk-js@5.22.3 + ## 2.2.13 ### Patch Changes diff --git a/packages/expo/package.json b/packages/expo/package.json index 8db725afdfd..21a910518ba 100644 --- a/packages/expo/package.json +++ b/packages/expo/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/clerk-expo", - "version": "2.2.13", + "version": "2.2.16", "description": "Clerk React Native/Expo library", "keywords": [ "react", @@ -55,10 +55,10 @@ "publish:local": "npx yalc push --replace --sig" }, "dependencies": { - "@clerk/clerk-js": "5.22.2", - "@clerk/clerk-react": "5.9.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/clerk-js": "5.23.0", + "@clerk/clerk-react": "5.9.3", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "base-64": "^1.0.0", "react-native-url-polyfill": "2.0.0", "tslib": "2.4.1" diff --git a/packages/expo/src/provider/ClerkProvider.tsx b/packages/expo/src/provider/ClerkProvider.tsx index 7b6b692771b..b3bf215a246 100644 --- a/packages/expo/src/provider/ClerkProvider.tsx +++ b/packages/expo/src/provider/ClerkProvider.tsx @@ -8,6 +8,10 @@ import { isNative, isWeb } from '../utils/runtime'; import { getClerkInstance } from './singleton'; export type ClerkProviderProps = React.ComponentProps & { + /** + * The token cache is used to persist the active user's session token. Clerk stores this token in memory by default, however it is recommended to use a token cache for production applications. + * @see https://clerk.com/docs/quickstarts/expo#configure-the-token-cache-with-expo + */ tokenCache?: TokenCache; }; diff --git a/packages/express/CHANGELOG.md b/packages/express/CHANGELOG.md index 9cc84d416db..53a66feff47 100644 --- a/packages/express/CHANGELOG.md +++ b/packages/express/CHANGELOG.md @@ -1,5 +1,34 @@ # Change Log +## 1.0.1 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + - @clerk/backend@1.13.4 + - @clerk/shared@2.8.3 + +## 1.0.0 + +### Major Changes + +- Add support for Express 5 ([#4201](https://github.com/clerk/javascript/pull/4201)) by [@wobsoriano](https://github.com/wobsoriano) + +### Patch Changes + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`418be2fdb`](https://github.com/clerk/javascript/commit/418be2fdb558bb5c85d7be491945935b44cad681), [`c59636a1a`](https://github.com/clerk/javascript/commit/c59636a1aca67be7d6732d281cec307ed456678b), [`5c18671f1`](https://github.com/clerk/javascript/commit/5c18671f158f8077f822877ce5c1fa192199aeda), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529), [`e0ca9dc94`](https://github.com/clerk/javascript/commit/e0ca9dc94fa68f3d3db5d2433fa6b85d800d4ca2)]: + - @clerk/shared@2.8.2 + - @clerk/types@4.21.1 + - @clerk/backend@1.13.3 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies [[`02babaccb`](https://github.com/clerk/javascript/commit/02babaccb648fa4e22f38cc0f572d44f82b09f78)]: + - @clerk/backend@1.13.2 + ## 0.1.2 ### Patch Changes diff --git a/packages/express/package.json b/packages/express/package.json index ea2c2c84eeb..b878d606a89 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/express", - "version": "0.1.2", + "version": "1.0.1", "description": "Clerk server SDK for usage with Express", "keywords": [ "clerk", @@ -53,9 +53,9 @@ "test:ci": "jest --maxWorkers=70%" }, "dependencies": { - "@clerk/backend": "^1.13.1", - "@clerk/shared": "^2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "^1.13.4", + "@clerk/shared": "^2.8.3", + "@clerk/types": "4.22.0", "tslib": "2.4.1" }, "devDependencies": { @@ -68,6 +68,9 @@ "tsup": "*", "typescript": "*" }, + "peerDependencies": { + "express": "^4.17.0 || ^5.0.0" + }, "engines": { "node": ">=18.17.0" }, diff --git a/packages/express/src/__tests__/clerkMiddleware.test.ts b/packages/express/src/__tests__/clerkMiddleware.test.ts index e57da8a6234..2ae6308b8df 100644 --- a/packages/express/src/__tests__/clerkMiddleware.test.ts +++ b/packages/express/src/__tests__/clerkMiddleware.test.ts @@ -70,9 +70,6 @@ describe('clerkMiddleware', () => { it('supports usage without parameters: app.use(clerkMiddleware())', async () => { await runMiddleware(clerkMiddleware(), { Cookie: '__clerk_db_jwt=deadbeef;' }).expect(200, 'Hello world!'); - - // TODO: Observability headers are not added by default - // assertSignedOutDebugHeaders(response); }); it('supports usage with parameters: app.use(clerkMiddleware(options))', async () => { @@ -140,6 +137,15 @@ describe('clerkMiddleware', () => { expect(response.header).not.toHaveProperty('x-clerk-auth-custom', 'custom-value'); }); + it('disables handshake flow by default', async () => { + const response = await runMiddleware(clerkMiddleware(), { + Cookie: '__client_uat=1711618859;', + 'Sec-Fetch-Dest': 'document', + }).expect(200); + + assertNoDebugHeaders(response); + }); + it('supports handshake flow', async () => { const response = await runMiddleware(clerkMiddleware({ enableHandshake: true }), { Cookie: '__client_uat=1711618859;', @@ -150,7 +156,7 @@ describe('clerkMiddleware', () => { expect(response.header).toHaveProperty('location', expect.stringContaining('/v1/client/handshake?redirect_url=')); }); - it('it calls next with an error when request URL is invalid', async () => { + it('calls next with an error when request URL is invalid', () => { const req = { url: '//', cookies: {}, @@ -159,7 +165,7 @@ describe('clerkMiddleware', () => { const res = {} as Response; const mockNext = jest.fn(); - await clerkMiddleware()[0](req, res, mockNext); + clerkMiddleware()[0](req, res, mockNext); expect(mockNext.mock.calls[0][0].message).toBe('Invalid URL'); diff --git a/packages/fastify/CHANGELOG.md b/packages/fastify/CHANGELOG.md index e44b0e0fb5c..def1e54870a 100644 --- a/packages/fastify/CHANGELOG.md +++ b/packages/fastify/CHANGELOG.md @@ -1,5 +1,30 @@ # Change Log +## 1.0.47 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + - @clerk/backend@1.13.4 + - @clerk/shared@2.8.3 + +## 1.0.46 + +### Patch Changes + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`418be2fdb`](https://github.com/clerk/javascript/commit/418be2fdb558bb5c85d7be491945935b44cad681), [`c59636a1a`](https://github.com/clerk/javascript/commit/c59636a1aca67be7d6732d281cec307ed456678b), [`5c18671f1`](https://github.com/clerk/javascript/commit/5c18671f158f8077f822877ce5c1fa192199aeda), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529), [`e0ca9dc94`](https://github.com/clerk/javascript/commit/e0ca9dc94fa68f3d3db5d2433fa6b85d800d4ca2)]: + - @clerk/shared@2.8.2 + - @clerk/types@4.21.1 + - @clerk/backend@1.13.3 + +## 1.0.45 + +### Patch Changes + +- Updated dependencies [[`02babaccb`](https://github.com/clerk/javascript/commit/02babaccb648fa4e22f38cc0f572d44f82b09f78)]: + - @clerk/backend@1.13.2 + ## 1.0.44 ### Patch Changes diff --git a/packages/fastify/package.json b/packages/fastify/package.json index cd85098c836..7c7b7877591 100644 --- a/packages/fastify/package.json +++ b/packages/fastify/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/fastify", - "version": "1.0.44", + "version": "1.0.47", "description": "Clerk SDK for Fastify", "keywords": [ "auth", @@ -40,9 +40,9 @@ "test:cache:clear": "jest --clearCache --useStderr" }, "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "cookies": "0.8.0" }, "devDependencies": { diff --git a/packages/localizations/CHANGELOG.md b/packages/localizations/CHANGELOG.md index 0f7e227a573..0160b496974 100644 --- a/packages/localizations/CHANGELOG.md +++ b/packages/localizations/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## 3.0.6 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + +## 3.0.5 + +### Patch Changes + +- Updated dependencies [[`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7)]: + - @clerk/types@4.21.1 + +## 3.0.4 + +### Patch Changes + +- nl-NL localization updates ([#4181](https://github.com/clerk/javascript/pull/4181)) by [@guustgoossens](https://github.com/guustgoossens) + ## 3.0.3 ### Patch Changes diff --git a/packages/localizations/package.json b/packages/localizations/package.json index 9aae22b43b2..3963c149538 100644 --- a/packages/localizations/package.json +++ b/packages/localizations/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/localizations", - "version": "3.0.3", + "version": "3.0.6", "description": "Localizations for the Clerk components", "keywords": [ "react", @@ -99,7 +99,7 @@ "lint": "eslint src/" }, "dependencies": { - "@clerk/types": "4.21.0" + "@clerk/types": "4.22.0" }, "devDependencies": { "@clerk/eslint-config-custom": "*", diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index b8b3ce0a4a2..1d77357ae3a 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -16,74 +16,75 @@ export const nlNL: LocalizationResource = { locale: 'nl-NL', __experimental_userVerification: { alternativeMethods: { - actionLink: undefined, - actionText: undefined, - blockButton__backupCode: undefined, - blockButton__emailCode: undefined, - blockButton__password: undefined, - blockButton__phoneCode: undefined, - blockButton__totp: undefined, + actionLink: 'Krijg hulp', + actionText: 'Heb je geen van deze?', + blockButton__backupCode: 'Backupcode gebruiken', + blockButton__emailCode: 'Email code naar {{identifier}}', + blockButton__password: 'Doorgaan met je wachtwoord', + blockButton__phoneCode: 'Verzend SMS code naar {{identifier}}', + blockButton__totp: 'Use your authenticator app', getHelp: { - blockButton__emailSupport: undefined, - content: undefined, - title: undefined, + blockButton__emailSupport: 'Email ondersteuning', + content: + 'Als je moeite hebt om je account te verifiëren, email ons en we zullen met je werken om toegang te herstellen zo snel mogelijk.', + title: 'Krijg hulp', }, - subtitle: undefined, - title: undefined, + subtitle: 'Problemen? Je kunt een van deze methoden gebruiken voor verificatie.', + title: 'Gebruik een andere methode', }, backupCodeMfa: { - subtitle: undefined, - title: undefined, + subtitle: 'Je backupcode is de code die je kreeg bij het installeren van tweestapsverificatie.', + title: 'Backupcode invoeren', }, emailCode: { - formTitle: undefined, - resendButton: undefined, - subtitle: undefined, - title: undefined, + formTitle: 'Verificatiecode', + resendButton: 'Niet ontvangen? Opnieuw verzenden', + subtitle: 'om door te gaan naar {{applicationName}}', + title: 'Controleer je email', }, noAvailableMethods: { - message: undefined, - subtitle: undefined, - title: undefined, + message: 'Kan niet verder gaan met verificatie. Er is geen beschikbare verificatiefactor.', + subtitle: 'Er is een fout opgetreden', + title: 'Kan je account niet verifiëren', }, password: { - actionLink: undefined, - subtitle: undefined, - title: undefined, + actionLink: 'Gebruik een andere methode', + subtitle: 'Voer het wachtwoord in dat bij je account hoort', + title: 'Voer je wachtwoord in', }, phoneCode: { - formTitle: undefined, - resendButton: undefined, - subtitle: undefined, - title: undefined, + formTitle: 'Verificatiecode', + resendButton: 'Niet ontvangen? Opnieuw verzenden', + subtitle: 'om door te gaan naar {{applicationName}}', + title: 'Controleer je telefoon', }, phoneCodeMfa: { - formTitle: undefined, - resendButton: undefined, - subtitle: undefined, - title: undefined, + formTitle: 'Verificatiecode', + resendButton: 'Niet ontvangen? Opnieuw verzenden', + subtitle: 'om door te gaan naar {{applicationName}}', + title: 'Controleer je telefoon', }, totpMfa: { - formTitle: undefined, - subtitle: undefined, - title: undefined, + formTitle: 'Verificatiecode', + subtitle: 'om door te gaan naar {{applicationName}}', + title: 'Tweestapsverificatie', }, }, backButton: 'Terug', badge__default: 'Standaard', - badge__otherImpersonatorDevice: 'Ander immitatie apparaat', - badge__primary: 'Hoofd', - badge__requiresAction: 'Actie nodig', + badge__otherImpersonatorDevice: 'Ander impersonatie apparaat', + badge__primary: 'Primair', + badge__requiresAction: 'Actie vereist', badge__thisDevice: 'Dit apparaat', badge__unverified: 'Ongeverifieerd', badge__userDevice: 'Gebruikersapparaat', badge__you: 'Jij', createOrganization: { - formButtonSubmit: 'Maak organisatie aan', + formButtonSubmit: 'Creëer organisatie', invitePage: { formButtonReset: 'Overslaan', }, - title: 'Organisatie aanmaken', + title: 'Creëer organisatie', }, dates: { lastDay: "Gisteren om {{ date | timeString('nl-NL') }}", @@ -93,39 +94,38 @@ export const nlNL: LocalizationResource = { previous6Days: "Vorige {{ date | weekday('nl-NL','long') }} om {{ date | timeString('nl-NL') }}", sameDay: "Vandaag om {{ date | timeString('nl-NL') }}", }, - dividerText: 'or', + dividerText: 'of', footerActionLink__useAnotherMethod: 'Een andere methode gebruiken', - footerPageLink__help: 'Help', - footerPageLink__privacy: 'Privacy', - footerPageLink__terms: 'Voorwaarden', + footerPageLink__help: 'Helppagina: ', + footerPageLink__privacy: 'Privacybeleid: ', + footerPageLink__terms: 'Algemene voorwaarden: ', formButtonPrimary: 'Doorgaan', - formButtonPrimary__verify: 'Verify', + formButtonPrimary__verify: 'Verifieer', formFieldAction__forgotPassword: 'Wachtwoord vergeten?', - formFieldError__matchingPasswords: 'Passwords match.', - formFieldError__notMatchingPasswords: "Passwords don't match.", - formFieldError__verificationLinkExpired: 'The verification link expired. Please request a new link.', + formFieldError__matchingPasswords: 'Wachtwoorden matchen.', + formFieldError__notMatchingPasswords: 'Wachtwoorden komen niet overeen.', + formFieldError__verificationLinkExpired: 'De verificatielink is verlopen. Vraag een nieuwe link aan.', formFieldHintText__optional: 'Optioneel', - formFieldHintText__slug: 'A slug is a human-readable ID that must be unique. It’s often used in URLs.', + formFieldHintText__slug: 'Een slug is een mens-leesbaar ID die uniek moet zijn. Het wordt vaak gebruikt in URLs.', formFieldInputPlaceholder__backupCode: undefined, - formFieldInputPlaceholder__confirmDeletionUserAccount: 'Delete account', + formFieldInputPlaceholder__confirmDeletionUserAccount: 'Verwijder account', formFieldInputPlaceholder__emailAddress: undefined, formFieldInputPlaceholder__emailAddress_username: undefined, - formFieldInputPlaceholder__emailAddresses: - "Typ of plak één of meerdere emailadressen, gescheiden door spaties of komma's.", + formFieldInputPlaceholder__emailAddresses: 'example@email.com, example2@email.com', formFieldInputPlaceholder__firstName: undefined, formFieldInputPlaceholder__lastName: undefined, formFieldInputPlaceholder__organizationDomain: undefined, formFieldInputPlaceholder__organizationDomainEmailAddress: undefined, formFieldInputPlaceholder__organizationName: undefined, - formFieldInputPlaceholder__organizationSlug: undefined, + formFieldInputPlaceholder__organizationSlug: 'mijn-org', formFieldInputPlaceholder__password: undefined, formFieldInputPlaceholder__phoneNumber: undefined, formFieldInputPlaceholder__username: undefined, - formFieldLabel__automaticInvitations: 'Enable automatic invitations for this domain', + formFieldLabel__automaticInvitations: 'Automatische uitnodigingen inschakelen voor deze domein', formFieldLabel__backupCode: 'Backupcode', - formFieldLabel__confirmDeletion: 'Confirmation', + formFieldLabel__confirmDeletion: 'Bevestiging', formFieldLabel__confirmPassword: 'Wachtwoord bevestigen', - formFieldLabel__currentPassword: 'Current password', + formFieldLabel__currentPassword: 'Huidig wachtwoord', formFieldLabel__emailAddress: 'E-mailadres', formFieldLabel__emailAddress_username: 'E-mailadres of gebruikersnaam', formFieldLabel__emailAddresses: 'E-mailadressen', @@ -133,52 +133,52 @@ export const nlNL: LocalizationResource = { formFieldLabel__lastName: 'Achternaam', formFieldLabel__newPassword: 'Nieuw wachtwoord', formFieldLabel__organizationDomain: 'Domain', - formFieldLabel__organizationDomainDeletePending: 'Delete pending invitations and suggestions', - formFieldLabel__organizationDomainEmailAddress: 'Verification email address', + formFieldLabel__organizationDomainDeletePending: 'Verwijder uitnodigingen en suggesties', + formFieldLabel__organizationDomainEmailAddress: 'Verificatie-e-mailadres', formFieldLabel__organizationDomainEmailAddressDescription: - 'Enter an email address under this domain to receive a code and verify this domain.', + 'Voer een e-mailadres onder deze domein in om een code te ontvangen en deze domein te verifiëren.', formFieldLabel__organizationName: 'Organisatienaam', formFieldLabel__organizationSlug: 'Slug', - formFieldLabel__passkeyName: undefined, + formFieldLabel__passkeyName: 'Naam', formFieldLabel__password: 'Wachtwoord', formFieldLabel__phoneNumber: 'Telefoonnummer', formFieldLabel__role: 'Rol', - formFieldLabel__signOutOfOtherSessions: 'Sign out of all other devices', + formFieldLabel__signOutOfOtherSessions: 'Alle andere apparaten uitloggen', formFieldLabel__username: 'Gebruikersnaam', impersonationFab: { action__signOut: 'Uitloggen', title: 'Ingelogd als {{identifier}}', }, - maintenanceMode: undefined, - membershipRole__admin: 'Admin', + maintenanceMode: 'Onderhoudsmodus', + membershipRole__admin: 'Beheerder', membershipRole__basicMember: 'Lid', membershipRole__guestMember: 'Gast', organizationList: { - action__createOrganization: 'Create organization', - action__invitationAccept: 'Join', - action__suggestionsAccept: 'Request to join', - createOrganization: 'Create Organization', - invitationAcceptedLabel: 'Joined', - subtitle: 'to continue to {{applicationName}}', - suggestionsAcceptedLabel: 'Pending approval', - title: 'Choose an account', - titleWithoutPersonal: 'Choose an organization', + action__createOrganization: 'Creëer organisatie', + action__invitationAccept: 'Toetreden', + action__suggestionsAccept: 'Verzoek om toetreden', + createOrganization: 'Creëer organisatie', + invitationAcceptedLabel: 'Toegetreden', + subtitle: 'om door te gaan naar {{applicationName}}', + suggestionsAcceptedLabel: 'In behandeling', + title: 'Kies een organisatie', + titleWithoutPersonal: 'Kies een organisatie', }, organizationProfile: { badge__automaticInvitation: 'Automatic invitations', badge__automaticSuggestion: 'Automatic suggestions', - badge__manualInvitation: 'No automatic enrollment', - badge__unverified: 'Unverified', + badge__manualInvitation: 'Geen automatische inschrijving', + badge__unverified: 'Ongeverifieerd', createDomainPage: { subtitle: - 'Add the domain to verify. Users with email addresses at this domain can join the organization automatically or request to join.', - title: 'Add domain', + 'Voeg het domein toe om te verifiëren. Gebruikers met e-mailadressen in dit domein kunnen de organisatie automatisch toegang krijgen of een verzoek om toegang te maken.', + title: 'Domein toevoegen', }, invitePage: { detailsTitle__inviteFailed: 'De uitnodigingen konden niet verzonden worden. Los het volgende op en probeer het opnieuw:', formButtonPrimary__continue: 'Uitnodigingen verzenden', - selectDropdown__role: 'Select role', + selectDropdown__role: 'Selecteer rol', subtitle: 'Nodig nieuwe leden uit voor deze organisatie', successMessage: 'Uitnodigingen succesvol verzonden', title: 'Leden uitnodigen', @@ -196,11 +196,11 @@ export const nlNL: LocalizationResource = { invitationsTab: { autoInvitations: { headerSubtitle: - 'Invite users by connecting an email domain with your organization. Anyone who signs up with a matching email domain will be able to join the organization anytime.', - headerTitle: 'Automatic invitations', - primaryButton: 'Manage verified domains', + 'Uitnodig gebruikers door een domein toe te voegen aan je organisatie. Iedereen die zich aanmeldt met een e-mailadres in dit domein kan de organisatie automatisch toegang krijgen of een verzoek om toegang te maken.', + headerTitle: 'Automatische uitnodigingen', + primaryButton: 'Beheer geverifieerde domeinen', }, - table__emptyRow: 'No invitations to display', + table__emptyRow: 'Geen uitnodigingen gevonden', }, invitedMembersTab: { menuAction__revoke: 'Uitnodiging intrekken', @@ -209,38 +209,38 @@ export const nlNL: LocalizationResource = { requestsTab: { autoSuggestions: { headerSubtitle: - 'Users who sign up with a matching email domain, will be able to see a suggestion to request to join your organization.', - headerTitle: 'Automatic suggestions', - primaryButton: 'Manage verified domains', + 'Gebruikers die zich aanmelden met een e-mailadres in dit domein, kunnen een verzoek om toegang tot de organisatie zien.', + headerTitle: 'Automatische suggesties', + primaryButton: 'Beheer geverifieerde domeinen', }, - menuAction__approve: 'Approve', - menuAction__reject: 'Reject', - tableHeader__requested: 'Requested access', - table__emptyRow: 'No requests to display', + menuAction__approve: 'Goedkeuren', + menuAction__reject: 'Weigeren', + tableHeader__requested: 'Verzoek om toegang', + table__emptyRow: 'Geen verzoeken om toegang', }, start: { - headerTitle__invitations: 'Invitations', - headerTitle__members: 'Members', - headerTitle__requests: 'Requests', + headerTitle__invitations: 'Uitnodigingen', + headerTitle__members: 'Leden', + headerTitle__requests: 'Verzoeken', }, }, navbar: { - description: 'Manage your organization.', - general: 'General', - members: 'Members', - title: 'Organization', + description: 'Beheer je organisatie.', + general: 'Algemeen', + members: 'Leden', + title: 'Organisatie', }, profilePage: { dangerSection: { deleteOrganization: { - actionDescription: 'Type "{{organizationName}}" below to continue.', - messageLine1: 'Are you sure you want to delete this organization?', - messageLine2: 'This action is permanent and irreversible.', - successMessage: 'You have deleted the organization.', - title: 'Delete organization', + actionDescription: 'Typ "{{organizationName}}" hieronder om door te gaan.', + messageLine1: 'Weet je zeker dat je deze organisatie wilt verwijderen?', + messageLine2: 'Deze actie is permanent en onomkeerbaar.', + successMessage: 'Je hebt deze organisatie verwijderd.', + title: 'Organisatie verwijderen', }, leaveOrganization: { - actionDescription: 'Type "{{organizationName}}" below to continue.', + actionDescription: 'Typ "{{organizationName}}" hieronder om door te gaan.', messageLine1: 'Weet je zeker dat je deze organisatie wilt verlaten? Je zult toegang verliezen tot deze organisatie en haar applicaties.', messageLine2: 'Deze actie is permanent en onomkeerbaar.', @@ -250,77 +250,79 @@ export const nlNL: LocalizationResource = { title: 'Gevaar', }, domainSection: { - menuAction__manage: 'Manage', - menuAction__remove: 'Delete', - menuAction__verify: 'Verify', - primaryButton: 'Add domain', + menuAction__manage: 'Beheer', + menuAction__remove: 'Verwijder', + menuAction__verify: 'Verifieer', + primaryButton: 'Domein toevoegen', subtitle: - 'Allow users to join the organization automatically or request to join based on a verified email domain.', - title: 'Verified domains', + 'Laat gebruikers de organisatie automatisch toegang krijgen of een verzoek om toegang maken op basis van een geverifieerd e-maildomein.', + title: 'Geverifieerde domeinen', }, successMessage: 'De organisatie is bijgewerkt.', title: 'Organisatieprofiel', }, removeDomainPage: { - messageLine1: 'The email domain {{domain}} will be removed.', - messageLine2: 'Users won’t be able to join the organization automatically after this.', - successMessage: '{{domain}} has been removed.', - title: 'Remove domain', + messageLine1: 'Het e-maildomein {{domain}} wordt verwijderd.', + messageLine2: + 'Gebruikers kunnen de organisatie niet meer automatisch toegang krijgen na dit domein te verwijderen.', + successMessage: '{{domain}} is verwijderd.', + title: 'Domein verwijderen', }, start: { - headerTitle__general: 'General', + headerTitle__general: 'Algemeen', headerTitle__members: 'Leden', profileSection: { - primaryButton: undefined, - title: 'Organization Profile', + primaryButton: '', + title: 'Organisatieprofiel', uploadAction__title: 'Logo', }, }, verifiedDomainPage: { dangerTab: { - calloutInfoLabel: 'Removing this domain will affect invited users.', - removeDomainActionLabel__remove: 'Remove domain', - removeDomainSubtitle: 'Remove this domain from your verified domains', - removeDomainTitle: 'Remove domain', + calloutInfoLabel: 'Verwijderen van dit domein zal uitnodigingen beïnvloeden.', + removeDomainActionLabel__remove: 'Domein verwijderen', + removeDomainSubtitle: 'Verwijder dit domein van je geverifieerde domeinen', + removeDomainTitle: 'Domein verwijderen', }, enrollmentTab: { automaticInvitationOption__description: - 'Users are automatically invited to join the organization when they sign-up and can join anytime.', - automaticInvitationOption__label: 'Automatic invitations', + 'Gebruikers worden automatisch uitgenodigd om lid te worden van de organisatie wanneer ze zich aanmelden en kunnen lid worden wanneer ze dat willen.', + automaticInvitationOption__label: 'Automatische uitnodigingen', automaticSuggestionOption__description: - 'Users receive a suggestion to request to join, but must be approved by an admin before they are able to join the organization.', - automaticSuggestionOption__label: 'Automatic suggestions', - calloutInfoLabel: 'Changing the enrollment mode will only affect new users.', - calloutInvitationCountLabel: 'Pending invitations sent to users: {{count}}', - calloutSuggestionCountLabel: 'Pending suggestions sent to users: {{count}}', - manualInvitationOption__description: 'Users can only be invited manually to the organization.', - manualInvitationOption__label: 'No automatic enrollment', - subtitle: 'Choose how users from this domain can join the organization.', + 'Gebruikers ontvangen een aanbeveling om lid te worden, maar moeten worden goedgekeurd door een beheerder voordat ze toegang kunnen krijgen tot de organisatie.', + automaticSuggestionOption__label: 'Automatische suggesties', + calloutInfoLabel: 'Wijziging van de inschrijfmodus heeft alleen invloed op nieuwe gebruikers.', + calloutInvitationCountLabel: 'Uitnodigingen verzonden aan gebruikers: {{count}}', + calloutSuggestionCountLabel: 'Aanbevelingen verzonden aan gebruikers: {{count}}', + manualInvitationOption__description: + 'Gebruikers kunnen alleen handmatig worden uitgenodigd voor de organisatie.', + manualInvitationOption__label: 'Geen automatische inschrijving', + subtitle: 'Kies hoe gebruikers van dit domein toegang kunnen krijgen tot de organisatie.', }, start: { headerTitle__danger: 'Danger', - headerTitle__enrollment: 'Enrollment options', + headerTitle__enrollment: 'Inschrijfopties', }, - subtitle: 'The domain {{domain}} is now verified. Continue by selecting enrollment mode.', + subtitle: 'Het domein {{domain}} is nu geverifieerd. Ga verder door de inschrijfmodus te selecteren.', title: 'Update {{domain}}', }, verifyDomainPage: { - formSubtitle: 'Enter the verification code sent to your email address', - formTitle: 'Verification code', - resendButton: "Didn't receive a code? Resend", - subtitle: 'The domain {{domainName}} needs to be verified via email.', + formSubtitle: 'Voer de verificatiecode in die verzonden is naar je e-mailadres', + formTitle: 'Verificatiecode', + resendButton: 'Niet ontvangen? Verstuur opnieuw', + subtitle: 'Het domein {{domainName}} moet worden geverifieerd via e-mail.', subtitleVerificationCodeScreen: 'A verification code was sent to {{emailAddress}}. Enter the code to continue.', - title: 'Verify domain', + title: 'Verifieer domein', }, }, organizationSwitcher: { action__createOrganization: 'Maak organisatie aan', action__invitationAccept: 'Join', action__manageOrganization: 'Beheer organisatie', - action__suggestionsAccept: 'Request to join', + action__suggestionsAccept: 'Verzoek om lid te worden', notSelected: 'Geen organisatie geselecteerd', personalWorkspace: 'Persoonlijke werkruimte', - suggestionsAcceptedLabel: 'Pending approval', + suggestionsAcceptedLabel: 'In behandeling', }, paginationButton__next: 'Volgende', paginationButton__previous: 'Vorige', @@ -328,14 +330,14 @@ export const nlNL: LocalizationResource = { paginationRowText__of: 'van', signIn: { accountSwitcher: { - action__addAccount: 'Add account', - action__signOutAll: 'Sign out of all accounts', - subtitle: 'Select the account with which you wish to continue.', - title: 'Choose an account', + action__addAccount: 'Account toevoegen', + action__signOutAll: 'Uitloggen van alle accounts', + subtitle: 'Selecteer het account met welk je door wilt gaan.', + title: 'Kies een account', }, alternativeMethods: { actionLink: 'Help', - actionText: 'Don’t have any of these?', + actionText: 'Heb je geen van deze?', blockButton__backupCode: 'Gebruik een backupcode', blockButton__emailCode: 'Verzend code naar {{identifier}}', blockButton__emailLink: 'Verzend link naar {{identifier}}', @@ -348,7 +350,7 @@ export const nlNL: LocalizationResource = { content: 'Als je geen toegang hebt neem dan contact op met de klantenservice en we helpen je verder.', title: 'Help', }, - subtitle: 'Facing issues? You can use any of these methods to sign in.', + subtitle: 'Problemen? Je kan een van deze methoden gebruiken om in te loggen.', title: 'Gebruik een andere methode', }, backupCodeMfa: { @@ -397,22 +399,22 @@ export const nlNL: LocalizationResource = { }, }, forgotPassword: { - formTitle: 'Reset password code', - resendButton: "Didn't receive a code? Resend", - subtitle: 'to reset your password', - subtitle_email: 'First, enter the code sent to your email ID', - subtitle_phone: 'First, enter the code sent to your phone', - title: 'Reset password', + formTitle: 'Wachtwoord resetcode', + resendButton: 'Niet ontvangen? Verstuur opnieuw', + subtitle: 'om door te gaan naar {{applicationName}}', + subtitle_email: 'Voer de code in die verzonden is naar je e-mailadres', + subtitle_phone: 'Voer de code in die verzonden is naar je telefoon', + title: 'Wachtwoord resetten', }, forgotPasswordAlternativeMethods: { - blockButton__resetPassword: 'Reset your password', - label__alternativeMethods: 'Or, sign in with another method', - title: 'Forgot Password?', + blockButton__resetPassword: 'Wachtwoord resetten', + label__alternativeMethods: 'Of, inloggen met een andere methode', + title: 'Wachtwoord vergeten?', }, noAvailableMethods: { message: 'Het is niet mogelijk om door te gaan met inloggen. Er is geen beschikbare authenticatiefactor.', subtitle: 'Er heeft zich een fout voorgedaan', - title: 'Inloggen onmogelijk', + title: 'Inloggen niet mogelijk', }, passkey: { subtitle: undefined, @@ -427,40 +429,40 @@ export const nlNL: LocalizationResource = { title: undefined, }, phoneCode: { - formTitle: 'Verificatie code', + formTitle: 'Verificatiecode', resendButton: 'Verstuur code opnieuw', subtitle: 'om verder te gaan naar {{applicationName}}', title: 'Check je telefoon', }, phoneCodeMfa: { - formTitle: 'Verificatie code', + formTitle: 'Verificatiecode', resendButton: 'Verstuur code opnieuw', - subtitle: undefined, + subtitle: '', title: 'Check je telefoon', }, resetPassword: { - formButtonPrimary: 'Reset Password', - requiredMessage: 'For security reasons, it is required to reset your password.', - successMessage: 'Your password was successfully changed. Signing you in, please wait a moment.', - title: 'Set new password', + formButtonPrimary: 'Wachtwoord resetten', + requiredMessage: 'Voor veiligheidsredenen is het vereist om je wachtwoord te resetten.', + successMessage: 'Je wachtwoord is succesvol gewijzigd. We sturen je door naar de inlogpagina.', + title: 'Wachtwoord resetten', }, resetPasswordMfa: { - detailsLabel: 'We need to verify your identity before resetting your password.', + detailsLabel: 'Voor veiligheidsredenen is het vereist om je wachtwoord te resetten.', }, start: { actionLink: 'Registreren', - actionLink__use_email: 'Use email', - actionLink__use_email_username: 'Use email or username', + actionLink__use_email: 'Gebruik e-mail', + actionLink__use_email_username: 'Gebruik e-mail of gebruikersnaam', actionLink__use_passkey: undefined, - actionLink__use_phone: 'Use phone', - actionLink__use_username: 'Use username', + actionLink__use_phone: 'Gebruik telefoon', + actionLink__use_username: 'Gebruik gebruikersnaam', actionText: 'Geen account?', subtitle: 'om door te gaan naar {{applicationName}}', - title: 'Log in', + title: 'Inloggen', }, totpMfa: { formTitle: 'Verificatiecode', - subtitle: undefined, + subtitle: '', title: 'Tweestapsverificatie', }, }, @@ -510,69 +512,63 @@ export const nlNL: LocalizationResource = { }, start: { actionLink: 'Inloggen', - actionLink__use_email: undefined, - actionLink__use_phone: undefined, + actionLink__use_email: 'Gebruik e-mail', + actionLink__use_phone: 'Gebruik telefoon', actionText: 'Heb je al een account?', subtitle: 'om door te gaan naar {{applicationName}}', title: 'Maak je account aan', }, }, socialButtonsBlockButton: 'Ga verder met {{provider|titleize}}', - socialButtonsBlockButtonManyInView: undefined, + socialButtonsBlockButtonManyInView: 'Ga verder met {{provider|titleize}}', unstable__errors: { - already_a_member_in_organization: undefined, captcha_invalid: 'Sign up unsuccessful due to failed security validations. Please refresh the page to try again or reach out to support for more assistance.', captcha_unavailable: 'Sign up unsuccessful due to failed bot validation. Please refresh the page to try again or reach out to support for more assistance.', form_code_incorrect: undefined, form_identifier_exists: undefined, - form_identifier_exists__email_address: undefined, - form_identifier_exists__phone_number: undefined, - form_identifier_exists__username: undefined, + form_identifier_exists__email_address: 'Dit e-mailadres is al in gebruik.', + form_identifier_exists__phone_number: 'Dit telefoonnummer is al in gebruik.', + form_identifier_exists__username: 'Deze gebruikersnaam is al in gebruik.', form_identifier_not_found: undefined, form_param_format_invalid: undefined, - form_param_format_invalid__email_address: 'Email address must be a valid email address.', - form_param_format_invalid__phone_number: 'Phone number must be in a valid international format', - form_param_max_length_exceeded__first_name: 'First name should not exceed 256 characters.', - form_param_max_length_exceeded__last_name: 'Last name should not exceed 256 characters.', - form_param_max_length_exceeded__name: 'Name should not exceed 256 characters.', + form_param_format_invalid__email_address: 'E-mailadres moet een geldig e-mailadres zijn.', + form_param_format_invalid__phone_number: 'Telefoonnummer moet een geldig internationaal nummer zijn.', + form_param_max_length_exceeded__first_name: 'Voornaam moet minder dan 256 tekens bevatten.', + form_param_max_length_exceeded__last_name: 'Achternaam moet minder dan 256 tekens bevatten.', + form_param_max_length_exceeded__name: 'Naam moet minder dan 256 tekens bevatten.', form_param_nil: undefined, - form_param_value_invalid: undefined, form_password_incorrect: undefined, form_password_length_too_short: undefined, - form_password_not_strong_enough: 'Your password is not strong enough.', - form_password_pwned: undefined, - form_password_pwned__sign_in: undefined, + form_password_not_strong_enough: 'Je wachtwoord is niet sterk genoeg.', + form_password_pwned: 'Dit wachtwoord is in een datalek gevonden.', + form_password_pwned__sign_in: 'Als je dit wachtwoord elders gebruikt, moet je het wijzigen.', form_password_size_in_bytes_exceeded: - 'Your password has exceeded the maximum number of bytes allowed, please shorten it or remove some special characters.', - form_password_validation_failed: 'Incorrect Password', + 'Je wachtwoord heeft het maximum aantal bytes overschreven, vermijd speciale tekens.', + form_password_validation_failed: 'Wachtwoord is incorrect.', form_username_invalid_character: undefined, form_username_invalid_length: undefined, - identification_deletion_failed: 'You cannot delete your last identification.', + identification_deletion_failed: 'Je kunt je laatste identificatie niet verwijderen.', not_allowed_access: undefined, - organization_domain_blocked: undefined, - organization_domain_common: undefined, - organization_membership_quota_exceeded: undefined, - organization_minimum_permissions_needed: undefined, - passkey_already_exists: undefined, - passkey_not_supported: undefined, - passkey_pa_not_supported: undefined, - passkey_registration_cancelled: undefined, - passkey_retrieval_cancelled: undefined, + passkey_already_exists: 'Deze passkey bestaat al.', + passkey_not_supported: 'Passkeys worden niet ondersteund door deze browser.', + passkey_pa_not_supported: 'Passkeys worden niet ondersteund door deze browser.', + passkey_registration_cancelled: 'Passkey registratie is geannuleerd.', + passkey_retrieval_cancelled: 'Passkey ophalen is geannuleerd.', passwordComplexity: { - maximumLength: undefined, - minimumLength: undefined, - requireLowercase: undefined, - requireNumbers: undefined, - requireSpecialCharacter: undefined, - requireUppercase: undefined, - sentencePrefix: undefined, - }, - phone_number_exists: 'This phone number is taken. Please try another.', + maximumLength: 'Wachtwoord moet minder dan 256 tekens bevatten.', + minimumLength: 'Wachtwoord moet minstens 8 tekens bevatten.', + requireLowercase: 'Wachtwoord moet minstens 1 kleine letter bevatten.', + requireNumbers: 'Wachtwoord moet minstens 1 cijfer bevatten.', + requireSpecialCharacter: 'Wachtwoord moet minstens 1 speciaal teken bevatten.', + requireUppercase: 'Wachtwoord moet minstens 1 hoofdletter bevatten.', + sentencePrefix: 'Wachtwoord moet minstens 1 speciaal teken bevatten.', + }, + phone_number_exists: 'Dit telefoonnummer is al in gebruik. Probeer een ander nummer.', zxcvbn: { - couldBeStronger: 'Your password works, but could be stronger. Try adding more characters.', - goodPassword: 'Your password meets all the necessary requirements.', + couldBeStronger: 'Je wachtwoord werkt, maar kan sterker zijn. Probeer meer tekens toe te voegen.', + goodPassword: 'Je wachtwoord voldoet aan alle vereisten.', notEnough: 'Je wachtwoord is niet sterk genoeg.', suggestions: { allUppercase: 'Zet een deel in hoofdletters, maar niet alle letters.', @@ -676,20 +672,20 @@ export const nlNL: LocalizationResource = { title: 'Verwijder e-mailadres', }, title: 'E-mailadres toevoegen', - verifyTitle: 'Verify email address', + verifyTitle: 'E-mailadres bevestigen', }, - formButtonPrimary__add: 'Add', + formButtonPrimary__add: 'Toevoegen', formButtonPrimary__continue: 'Doorgaan', formButtonPrimary__finish: 'Afronden', - formButtonPrimary__remove: 'Remove', - formButtonPrimary__save: 'Save', + formButtonPrimary__remove: 'Verwijderen', + formButtonPrimary__save: 'Opslaan', formButtonReset: 'Annuleren', mfaPage: { formHint: 'Kies een methode om toe te voegen.', title: 'Tweestapsverificatie toevoegen', }, mfaPhoneCodePage: { - backButton: 'Use existing number', + backButton: 'Gebruik bestaand nummer', primaryButton__addPhoneNumber: 'Telefoonnummer toevoegen', removeResource: { messageLine1: '{{identifier}} zal niet langer verificatiecodes ontvangen bij het inloggen.', @@ -704,15 +700,15 @@ export const nlNL: LocalizationResource = { successMessage1: 'When signing in, you will need to enter a verification code sent to this phone number as an additional step.', successMessage2: - 'Save these backup codes and store them somewhere safe. If you lose access to your authentication device, you can use backup codes to sign in.', - successTitle: 'SMS code verification enabled', + 'Sla deze backup codes op en bewaar ze ergens veilig. Als je toegang kwijtraakt tot je authenticatieapparaat, kun je de backup codes gebruiken om in te loggen.', + successTitle: 'SMS-code verificatie ingeschakeld', title: 'Voeg SMS-code verificatie toe', }, mfaTOTPPage: { authenticatorApp: { - buttonAbleToScan__nonPrimary: 'Alternatief, scan een QR code', + buttonAbleToScan__nonPrimary: 'Een tweede optie, scan de QR-code', buttonUnableToScan__nonPrimary: 'Kan je de code niet scannen?', - infoText__ableToScan: 'Scan de QR code met je authenticator app om de authenticator toe te voegen.', + infoText__ableToScan: 'Scan de QR-code met je authenticator app om de authenticator toe te voegen.', infoText__unableToScan: 'Stel een nieuwe aanmeldmethode in op je authenticator en voer de onderstaande sleutel in.', inputLabel__unableToScan1: @@ -733,48 +729,49 @@ export const nlNL: LocalizationResource = { }, mobileButton__menu: 'Menu', navbar: { - account: 'Profile', - description: 'Manage your account info.', - security: 'Security', + account: 'Profiel', + description: 'Beheer je account informatie.', + security: 'Beveiliging', title: 'Account', }, passkeyScreen: { removeResource: { - messageLine1: undefined, - title: undefined, + messageLine1: '{{name}} zal verwijderd worden uit dit account.', + title: 'Verwijder passkey', }, - subtitle__rename: undefined, - title__rename: undefined, + subtitle__rename: 'Je kunt de naam van de passkey wijzigen om deze gemakkelijker te vinden.', + title__rename: 'Passkey hernoemen', }, passwordPage: { checkboxInfoText__signOutOfOtherSessions: - 'It is recommended to sign out of all other devices which may have used your old password.', - readonly: 'Your password can currently not be edited because you can sign in only via the enterprise connection.', + 'Het is aanbevolen om uit te loggen van alle andere apparaten die mogelijk gebruik hebben gemaakt van je oude wachtwoord.', + readonly: + 'Je wachtwoord kan momenteel niet worden gewijzigd omdat je alleen via de enterprise connectie kunt inloggen.', successMessage__set: 'Je wachtwoord is ingesteld.', - successMessage__signOutOfOtherSessions: 'All other devices have been signed out.', - successMessage__update: 'Your password has been updated.', + successMessage__signOutOfOtherSessions: 'Alle andere apparaten zijn uitgelogd.', + successMessage__update: 'Je wachtwoord is bijgewerkt.', title__set: 'Stel wachtwoord in', - title__update: 'Change password', + title__update: 'Wachtwoord wijzigen', }, phoneNumberPage: { infoText: 'Een SMS met daarin een verificatiecode is verstuurd naar dit nummer.', removeResource: { - messageLine1: '{{identifier}} zal van deze account verwijderd worden.', + messageLine1: '{{identifier}} zal van dit account verwijderd worden.', messageLine2: 'Je zal niet meer kunnen inloggen met dit telefoonnummer.', successMessage: '{{phoneNumber}} is verwijderd uit je account.', title: 'Verwijder telefoonnummer', }, - successMessage: '{{identifier}} is toegevoegd aan je account.', + successMessage: '{{phoneNumber}} is toegevoegd aan je account.', title: 'Telefoonnummer toevoegen', - verifySubtitle: 'Enter the verification code sent to {{identifier}}', - verifyTitle: 'Verify phone number', + verifySubtitle: 'Voer de verificatiecode in die verstuurd is naar {{phoneNumber}}', + verifyTitle: 'Verifieer telefoonnummer', }, profilePage: { fileDropAreaHint: 'Upload een JPG, PNG, GIF, of WEBP afbeelding kleiner dan 10 MB', imageFormDestructiveActionSubtitle: 'Verwijder afbeelding', imageFormSubtitle: 'Afbeelding uploaden', imageFormTitle: 'Profielfoto', - readonly: 'Your profile information has been provided by the enterprise connection and cannot be edited.', + readonly: 'Je profiel informatie is verstrekt door de enterprise connectie en kan niet worden bewerkt.', successMessage: 'Je profiel is bijgewerkt.', title: 'Profiel bijwerken', }, @@ -790,12 +787,12 @@ export const nlNL: LocalizationResource = { primaryButton: 'Verbind een account', subtitle__disconnected: undefined, subtitle__reauthorize: - 'The required scopes have been updated, and you may be experiencing limited functionality. Please re-authorize this application to avoid any issues', + 'De vereiste scopes zijn bijgewerkt, en je kunt mogelijk beperkte functionaliteit ervaren. Autoriseer deze toepassing opnieuw om problemen te voorkomen.', title: 'Aangesloten accounts', }, dangerSection: { - deleteAccountButton: 'Delete Account', - title: 'Account termination', + deleteAccountButton: 'Verwijder account', + title: 'Account beëindigen', }, emailAddressesSection: { destructiveAction: 'Verwijder e-mailadres', @@ -806,20 +803,20 @@ export const nlNL: LocalizationResource = { title: 'E-mailadressen', }, enterpriseAccountsSection: { - title: 'Enterprise accounts', + title: 'Bedrijfsaccounts', }, headerTitle__account: 'Account', headerTitle__security: 'Beveiliging', mfaSection: { backupCodes: { - actionLabel__regenerate: 'Hergenereer codes', + actionLabel__regenerate: 'Codes hergenereren', headerTitle: 'Backupcodes', subtitle__regenerate: 'Genereer een nieuwe set backupcodes. De vorige kunnen niet meer gebruikt worden.', - title__regenerate: 'Hergenereer backupcodes', + title__regenerate: 'Backupcodes hergenereren', }, phoneCode: { actionLabel__setDefault: 'Stel in als standaard', - destructiveActionLabel: 'Verwijder telefoonnummer', + destructiveActionLabel: 'Verwijder tweestapsverificatie', }, primaryButton: 'Tweestapsverificatie instellen', title: 'Tweestapsverificatie', @@ -847,7 +844,7 @@ export const nlNL: LocalizationResource = { title: 'Telefoonnummers', }, profileSection: { - primaryButton: undefined, + primaryButton: 'Profiel bijwerken', title: 'Profiel', }, usernameSection: { diff --git a/packages/nextjs/CHANGELOG.md b/packages/nextjs/CHANGELOG.md index c2de5b6846c..2b02812b553 100644 --- a/packages/nextjs/CHANGELOG.md +++ b/packages/nextjs/CHANGELOG.md @@ -1,5 +1,36 @@ # Change Log +## 5.6.2 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + - @clerk/backend@1.13.4 + - @clerk/clerk-react@5.9.3 + - @clerk/shared@2.8.3 + +## 5.6.1 + +### Patch Changes + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`418be2fdb`](https://github.com/clerk/javascript/commit/418be2fdb558bb5c85d7be491945935b44cad681), [`c59636a1a`](https://github.com/clerk/javascript/commit/c59636a1aca67be7d6732d281cec307ed456678b), [`5c18671f1`](https://github.com/clerk/javascript/commit/5c18671f158f8077f822877ce5c1fa192199aeda), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529), [`e0ca9dc94`](https://github.com/clerk/javascript/commit/e0ca9dc94fa68f3d3db5d2433fa6b85d800d4ca2)]: + - @clerk/shared@2.8.2 + - @clerk/clerk-react@5.9.2 + - @clerk/types@4.21.1 + - @clerk/backend@1.13.3 + +## 5.6.0 + +### Minor Changes + +- Allows access to request object to dynamically define `clerkMiddleware` options ([#4160](https://github.com/clerk/javascript/pull/4160)) by [@LauraBeatris](https://github.com/LauraBeatris) + +### Patch Changes + +- Updated dependencies [[`02babaccb`](https://github.com/clerk/javascript/commit/02babaccb648fa4e22f38cc0f572d44f82b09f78)]: + - @clerk/backend@1.13.2 + ## 5.5.5 ### Patch Changes diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 20ac7fb2740..3567a5afbd6 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/nextjs", - "version": "5.5.5", + "version": "5.6.2", "description": "Clerk SDK for NextJS", "keywords": [ "clerk", @@ -67,10 +67,10 @@ "test:ci": "jest --maxWorkers=70%" }, "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/clerk-react": "5.9.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/clerk-react": "5.9.3", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "crypto-js": "4.2.0", "server-only": "0.0.1", "tslib": "2.4.1" diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 57cb73b12e1..0b569531e89 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## 5.9.3 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + - @clerk/shared@2.8.3 + +## 5.9.2 + +### Patch Changes + +- Improve JSDoc comments for some public API properties ([#4190](https://github.com/clerk/javascript/pull/4190)) by [@LekoArts](https://github.com/LekoArts) + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529)]: + - @clerk/shared@2.8.2 + - @clerk/types@4.21.1 + ## 5.9.1 ### Patch Changes diff --git a/packages/react/package.json b/packages/react/package.json index c6a0c41a053..1d505d9e6ce 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/clerk-react", - "version": "5.9.1", + "version": "5.9.3", "description": "Clerk React library", "keywords": [ "clerk", @@ -76,13 +76,13 @@ "test:ci": "jest --maxWorkers=70%" }, "dependencies": { - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "tslib": "2.4.1" }, "devDependencies": { "@clerk/eslint-config-custom": "*", - "@clerk/themes": "2.1.30", + "@clerk/themes": "2.1.32", "@types/node": "^18.19.33", "@types/react": "*", "@types/react-dom": "*", diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 63b92c48d90..2ad19fa125c 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -51,6 +51,9 @@ export type IsomorphicClerkOptions = Without & { export type ClerkProviderProps = IsomorphicClerkOptions & { children: React.ReactNode; + /** + * Provide an initial state of the Clerk client during server-side rendering (SSR) + */ initialState?: InitialState; }; diff --git a/packages/remix/CHANGELOG.md b/packages/remix/CHANGELOG.md index e4d29ebf23a..417c3948cb4 100644 --- a/packages/remix/CHANGELOG.md +++ b/packages/remix/CHANGELOG.md @@ -1,5 +1,32 @@ # Change Log +## 4.2.31 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + - @clerk/backend@1.13.4 + - @clerk/clerk-react@5.9.3 + - @clerk/shared@2.8.3 + +## 4.2.30 + +### Patch Changes + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`418be2fdb`](https://github.com/clerk/javascript/commit/418be2fdb558bb5c85d7be491945935b44cad681), [`c59636a1a`](https://github.com/clerk/javascript/commit/c59636a1aca67be7d6732d281cec307ed456678b), [`5c18671f1`](https://github.com/clerk/javascript/commit/5c18671f158f8077f822877ce5c1fa192199aeda), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529), [`e0ca9dc94`](https://github.com/clerk/javascript/commit/e0ca9dc94fa68f3d3db5d2433fa6b85d800d4ca2)]: + - @clerk/shared@2.8.2 + - @clerk/clerk-react@5.9.2 + - @clerk/types@4.21.1 + - @clerk/backend@1.13.3 + +## 4.2.29 + +### Patch Changes + +- Updated dependencies [[`02babaccb`](https://github.com/clerk/javascript/commit/02babaccb648fa4e22f38cc0f572d44f82b09f78)]: + - @clerk/backend@1.13.2 + ## 4.2.28 ### Patch Changes diff --git a/packages/remix/package.json b/packages/remix/package.json index 2096bf6e2b9..b4e95cb0667 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/remix", - "version": "4.2.28", + "version": "4.2.31", "description": "Clerk SDK for Remix", "keywords": [ "clerk", @@ -73,10 +73,10 @@ "publish:local": "npx yalc push --replace --sig" }, "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/clerk-react": "5.9.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/clerk-react": "5.9.3", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "cookie": "0.5.0", "tslib": "2.4.1" }, diff --git a/packages/sdk-node/CHANGELOG.md b/packages/sdk-node/CHANGELOG.md index ded863dc055..5cb235eb5f3 100644 --- a/packages/sdk-node/CHANGELOG.md +++ b/packages/sdk-node/CHANGELOG.md @@ -1,5 +1,30 @@ # Change Log +## 5.0.44 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + - @clerk/backend@1.13.4 + - @clerk/shared@2.8.3 + +## 5.0.43 + +### Patch Changes + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`418be2fdb`](https://github.com/clerk/javascript/commit/418be2fdb558bb5c85d7be491945935b44cad681), [`c59636a1a`](https://github.com/clerk/javascript/commit/c59636a1aca67be7d6732d281cec307ed456678b), [`5c18671f1`](https://github.com/clerk/javascript/commit/5c18671f158f8077f822877ce5c1fa192199aeda), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529), [`e0ca9dc94`](https://github.com/clerk/javascript/commit/e0ca9dc94fa68f3d3db5d2433fa6b85d800d4ca2)]: + - @clerk/shared@2.8.2 + - @clerk/types@4.21.1 + - @clerk/backend@1.13.3 + +## 5.0.42 + +### Patch Changes + +- Updated dependencies [[`02babaccb`](https://github.com/clerk/javascript/commit/02babaccb648fa4e22f38cc0f572d44f82b09f78)]: + - @clerk/backend@1.13.2 + ## 5.0.41 ### Patch Changes diff --git a/packages/sdk-node/package.json b/packages/sdk-node/package.json index 482302430a3..81f1cae53a2 100644 --- a/packages/sdk-node/package.json +++ b/packages/sdk-node/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/clerk-sdk-node", - "version": "5.0.41", + "version": "5.0.44", "description": "Clerk server SDK for usage with node", "keywords": [ "clerk", @@ -53,9 +53,9 @@ "test:ci": "jest --maxWorkers=70%" }, "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "tslib": "2.4.1" }, "devDependencies": { diff --git a/packages/shared/CHANGELOG.md b/packages/shared/CHANGELOG.md index c3842115272..521cc5710d6 100644 --- a/packages/shared/CHANGELOG.md +++ b/packages/shared/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## 2.8.3 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + +## 2.8.2 + +### Patch Changes + +- Exports `match` utility from the `path-to-regexp` lib. ([#4187](https://github.com/clerk/javascript/pull/4187)) by [@izaaklauer](https://github.com/izaaklauer) + +- Fix issue where class-based routers were unable to access their private members during the `pathname` and `searchParams` methods. ([#4197](https://github.com/clerk/javascript/pull/4197)) by [@dstaley](https://github.com/dstaley) + +- Internal change to move `iconImageUrl` util to `shared` package. ([#4188](https://github.com/clerk/javascript/pull/4188)) by [@alexcarpenter](https://github.com/alexcarpenter) + +- Updated dependencies [[`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7)]: + - @clerk/types@4.21.1 + ## 2.8.1 ### Patch Changes diff --git a/packages/shared/package.json b/packages/shared/package.json index 968507a73a1..107431537e3 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/shared", - "version": "2.8.1", + "version": "2.8.3", "description": "Internal package utils used by the Clerk SDKs", "repository": { "type": "git", @@ -95,7 +95,7 @@ "test:coverage": "jest --collectCoverage && open coverage/lcov-report/index.html" }, "dependencies": { - "@clerk/types": "4.21.0", + "@clerk/types": "4.22.0", "glob-to-regexp": "0.4.1", "js-cookie": "3.0.5", "std-env": "^3.7.0", diff --git a/packages/shared/src/compiled/path-to-regexp/index.d.ts b/packages/shared/src/compiled/path-to-regexp/index.d.ts new file mode 100644 index 00000000000..d05fd814dae --- /dev/null +++ b/packages/shared/src/compiled/path-to-regexp/index.d.ts @@ -0,0 +1,102 @@ +interface ParseOptions { + /** + * Set the default delimiter for repeat parameters. (default: `'/'`) + */ + delimiter?: string; + /** + * List of characters to automatically consider prefixes when parsing. + */ + prefixes?: string; +} +interface RegexpToFunctionOptions { + /** + * Function for decoding strings for params. + */ + decode?: (value: string, token: Key) => string; +} +/** + * A match result contains data about the path match. + */ +interface MatchResult

{ + path: string; + index: number; + params: P; +} +/** + * A match is either `false` (no match) or a match result. + */ +type Match

= false | MatchResult

; +/** + * The match function takes a string and returns whether it matched the path. + */ +type MatchFunction

= (path: string) => Match

; +/** + * Create path match function from `path-to-regexp` spec. + */ +declare function match

( + str: Path, + options?: ParseOptions & TokensToRegexpOptions & RegexpToFunctionOptions, +): MatchFunction

; +/** + * Metadata about a key. + */ +interface Key { + name: string | number; + prefix: string; + suffix: string; + pattern: string; + modifier: string; +} +interface TokensToRegexpOptions { + /** + * When `true` the regexp will be case sensitive. (default: `false`) + */ + sensitive?: boolean; + /** + * When `true` the regexp won't allow an optional trailing delimiter to match. (default: `false`) + */ + strict?: boolean; + /** + * When `true` the regexp will match to the end of the string. (default: `true`) + */ + end?: boolean; + /** + * When `true` the regexp will match from the beginning of the string. (default: `true`) + */ + start?: boolean; + /** + * Sets the final character for non-ending optimistic matches. (default: `/`) + */ + delimiter?: string; + /** + * List of characters that can also be "end" characters. + */ + endsWith?: string; + /** + * Encode path tokens for use in the `RegExp`. + */ + encode?: (value: string) => string; +} +/** + * Supported `path-to-regexp` input types. + */ +type Path = string | RegExp | Array; +/** + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + */ +declare function pathToRegexp(path: Path, keys?: Key[], options?: TokensToRegexpOptions & ParseOptions): RegExp; + +export { + type Match, + type MatchFunction, + type ParseOptions, + type Path, + type RegexpToFunctionOptions, + type TokensToRegexpOptions, + match, + pathToRegexp, +}; diff --git a/packages/shared/src/compiled/path-to-regexp/index.js b/packages/shared/src/compiled/path-to-regexp/index.js index d7e7902de18..7112d4ae2b8 100644 --- a/packages/shared/src/compiled/path-to-regexp/index.js +++ b/packages/shared/src/compiled/path-to-regexp/index.js @@ -1,238 +1,331 @@ -/* eslint-disable no-redeclare */ +/* eslint-disable no-redeclare, curly */ function _(r) { for (var n = [], e = 0; e < r.length; ) { - var t = r[e]; - if (t === '*' || t === '+' || t === '?') { - n.push({ type: 'MODIFIER', index: e, value: r[e++] }); + var a = r[e]; + if (a === '*' || a === '+' || a === '?') { + n.push({ + type: 'MODIFIER', + index: e, + value: r[e++], + }); continue; } - if (t === '\\') { - n.push({ type: 'ESCAPED_CHAR', index: e++, value: r[e++] }); + if (a === '\\') { + n.push({ + type: 'ESCAPED_CHAR', + index: e++, + value: r[e++], + }); continue; } - if (t === '{') { - n.push({ type: 'OPEN', index: e, value: r[e++] }); + if (a === '{') { + n.push({ + type: 'OPEN', + index: e, + value: r[e++], + }); continue; } - if (t === '}') { - n.push({ type: 'CLOSE', index: e, value: r[e++] }); + if (a === '}') { + n.push({ + type: 'CLOSE', + index: e, + value: r[e++], + }); continue; } - if (t === ':') { - for (var u = '', a = e + 1; a < r.length; ) { - var f = r.charCodeAt(a); - if ((f >= 48 && f <= 57) || (f >= 65 && f <= 90) || (f >= 97 && f <= 122) || f === 95) { - u += r[a++]; + if (a === ':') { + for (var u = '', t = e + 1; t < r.length; ) { + var c = r.charCodeAt(t); + if ((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95) { + u += r[t++]; continue; } break; } - if (!u) { - throw new TypeError('Missing parameter name at '.concat(e)); - } - n.push({ type: 'NAME', index: e, value: u }), (e = a); + if (!u) throw new TypeError('Missing parameter name at '.concat(e)); + n.push({ + type: 'NAME', + index: e, + value: u, + }), + (e = t); continue; } - if (t === '(') { - var l = 1, - d = '', - a = e + 1; - if (r[a] === '?') { - throw new TypeError('Pattern cannot start with "?" at '.concat(a)); - } - for (; a < r.length; ) { - if (r[a] === '\\') { - d += r[a++] + r[a++]; + if (a === '(') { + var o = 1, + m = '', + t = e + 1; + if (r[t] === '?') throw new TypeError('Pattern cannot start with "?" at '.concat(t)); + for (; t < r.length; ) { + if (r[t] === '\\') { + m += r[t++] + r[t++]; continue; } - if (r[a] === ')') { - if ((l--, l === 0)) { - a++; + if (r[t] === ')') { + if ((o--, o === 0)) { + t++; break; } - } else if (r[a] === '(' && (l++, r[a + 1] !== '?')) { - throw new TypeError('Capturing groups are not allowed at '.concat(a)); - } - d += r[a++]; - } - if (l) { - throw new TypeError('Unbalanced pattern at '.concat(e)); + } else if (r[t] === '(' && (o++, r[t + 1] !== '?')) + throw new TypeError('Capturing groups are not allowed at '.concat(t)); + m += r[t++]; } - if (!d) { - throw new TypeError('Missing pattern at '.concat(e)); - } - n.push({ type: 'PATTERN', index: e, value: d }), (e = a); + if (o) throw new TypeError('Unbalanced pattern at '.concat(e)); + if (!m) throw new TypeError('Missing pattern at '.concat(e)); + n.push({ + type: 'PATTERN', + index: e, + value: m, + }), + (e = t); continue; } - n.push({ type: 'CHAR', index: e, value: r[e++] }); + n.push({ + type: 'CHAR', + index: e, + value: r[e++], + }); } - return n.push({ type: 'END', index: e, value: '' }), n; + return ( + n.push({ + type: 'END', + index: e, + value: '', + }), + n + ); } -function D(r, n) { + +function F(r, n) { n === void 0 && (n = {}); for ( var e = _(r), - t = n.prefixes, - u = t === void 0 ? './' : t, - a = '[^'.concat(y(n.delimiter || '/#?'), ']+?'), - f = [], - l = 0, - d = 0, + a = n.prefixes, + u = a === void 0 ? './' : a, + t = n.delimiter, + c = t === void 0 ? '/#?' : t, + o = [], + m = 0, + h = 0, p = '', - c = function (v) { - if (d < e.length && e[d].type === v) { - return e[d++].value; - } + f = function (l) { + if (h < e.length && e[h].type === l) return e[h++].value; }, - w = function (v) { - var g = c(v); - if (g !== void 0) { - return g; - } - var h = e[d], - b = h.type, - N = h.index; - throw new TypeError('Unexpected '.concat(b, ' at ').concat(N, ', expected ').concat(v)); + w = function (l) { + var v = f(l); + if (v !== void 0) return v; + var E = e[h], + N = E.type, + S = E.index; + throw new TypeError('Unexpected '.concat(N, ' at ').concat(S, ', expected ').concat(l)); + }, + d = function () { + for (var l = '', v; (v = f('CHAR') || f('ESCAPED_CHAR')); ) l += v; + return l; }, - A = function () { - for (var v = '', g; (g = c('CHAR') || c('ESCAPED_CHAR')); ) { - v += g; + M = function (l) { + for (var v = 0, E = c; v < E.length; v++) { + var N = E[v]; + if (l.indexOf(N) > -1) return !0; } - return v; + return !1; + }, + A = function (l) { + var v = o[o.length - 1], + E = l || (v && typeof v == 'string' ? v : ''); + if (v && !E) + throw new TypeError('Must have text between two parameters, missing text after "'.concat(v.name, '"')); + return !E || M(E) ? '[^'.concat(s(c), ']+?') : '(?:(?!'.concat(s(E), ')[^').concat(s(c), '])+?'); }; - d < e.length; + h < e.length; ) { - var s = c('CHAR'), - C = c('NAME'), - E = c('PATTERN'); - if (C || E) { - var x = s || ''; - u.indexOf(x) === -1 && ((p += x), (x = '')), - p && (f.push(p), (p = '')), - f.push({ name: C || l++, prefix: x, suffix: '', pattern: E || a, modifier: c('MODIFIER') || '' }); + var T = f('CHAR'), + x = f('NAME'), + C = f('PATTERN'); + if (x || C) { + var g = T || ''; + u.indexOf(g) === -1 && ((p += g), (g = '')), + p && (o.push(p), (p = '')), + o.push({ + name: x || m++, + prefix: g, + suffix: '', + pattern: C || A(g), + modifier: f('MODIFIER') || '', + }); continue; } - var o = s || c('ESCAPED_CHAR'); - if (o) { - p += o; + var i = T || f('ESCAPED_CHAR'); + if (i) { + p += i; continue; } - p && (f.push(p), (p = '')); - var R = c('OPEN'); + p && (o.push(p), (p = '')); + var R = f('OPEN'); if (R) { - var x = A(), - T = c('NAME') || '', - i = c('PATTERN') || '', - m = A(); + var g = d(), + y = f('NAME') || '', + O = f('PATTERN') || '', + b = d(); w('CLOSE'), - f.push({ - name: T || (i ? l++ : ''), - pattern: T && !i ? a : i, - prefix: x, - suffix: m, - modifier: c('MODIFIER') || '', + o.push({ + name: y || (O ? m++ : ''), + pattern: y && !O ? A(g) : O, + prefix: g, + suffix: b, + modifier: f('MODIFIER') || '', }); continue; } w('END'); } - return f; + return o; +} + +function H(r, n) { + var e = [], + a = P(r, e, n); + return I(a, e, n); +} + +function I(r, n, e) { + e === void 0 && (e = {}); + var a = e.decode, + u = + a === void 0 + ? function (t) { + return t; + } + : a; + return function (t) { + var c = r.exec(t); + if (!c) return !1; + for ( + var o = c[0], + m = c.index, + h = Object.create(null), + p = function (w) { + if (c[w] === void 0) return 'continue'; + var d = n[w - 1]; + d.modifier === '*' || d.modifier === '+' + ? (h[d.name] = c[w].split(d.prefix + d.suffix).map(function (M) { + return u(M, d); + })) + : (h[d.name] = u(c[w], d)); + }, + f = 1; + f < c.length; + f++ + ) + p(f); + return { + path: o, + index: m, + params: h, + }; + }; } -function y(r) { + +function s(r) { return r.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1'); } -function O(r) { + +function D(r) { return r && r.sensitive ? '' : 'i'; } -function M(r, n) { - if (!n) { - return r; - } - for (var e = /\((?:\?<(.*?)>)?(?!\?)/g, t = 0, u = e.exec(r.source); u; ) { - n.push({ name: u[1] || t++, prefix: '', suffix: '', modifier: '', pattern: '' }), (u = e.exec(r.source)); - } + +function $(r, n) { + if (!n) return r; + for (var e = /\((?:\?<(.*?)>)?(?!\?)/g, a = 0, u = e.exec(r.source); u; ) + n.push({ + name: u[1] || a++, + prefix: '', + suffix: '', + modifier: '', + pattern: '', + }), + (u = e.exec(r.source)); return r; } -function S(r, n, e) { - var t = r.map(function (u) { + +function W(r, n, e) { + var a = r.map(function (u) { return P(u, n, e).source; }); - return new RegExp('(?:'.concat(t.join('|'), ')'), O(e)); + return new RegExp('(?:'.concat(a.join('|'), ')'), D(e)); } -function F(r, n, e) { - return H(D(r, e), n, e); + +function L(r, n, e) { + return U(F(r, e), n, e); } -function H(r, n, e) { + +function U(r, n, e) { e === void 0 && (e = {}); for ( - var t = e.strict, - u = t === void 0 ? !1 : t, - a = e.start, - f = a === void 0 ? !0 : a, - l = e.end, - d = l === void 0 ? !0 : l, - p = e.encode, - c = - p === void 0 - ? function (N) { - return N; + var a = e.strict, + u = a === void 0 ? !1 : a, + t = e.start, + c = t === void 0 ? !0 : t, + o = e.end, + m = o === void 0 ? !0 : o, + h = e.encode, + p = + h === void 0 + ? function (v) { + return v; } - : p, - w = e.delimiter, - A = w === void 0 ? '/#?' : w, - s = e.endsWith, - C = s === void 0 ? '' : s, - E = '['.concat(y(C), ']|$'), - x = '['.concat(y(A), ']'), - o = f ? '^' : '', - R = 0, - T = r; - R < T.length; - R++ + : h, + f = e.delimiter, + w = f === void 0 ? '/#?' : f, + d = e.endsWith, + M = d === void 0 ? '' : d, + A = '['.concat(s(M), ']|$'), + T = '['.concat(s(w), ']'), + x = c ? '^' : '', + C = 0, + g = r; + C < g.length; + C++ ) { - var i = T[R]; - if (typeof i == 'string') { - o += y(c(i)); - } else { - var m = y(c(i.prefix)), - v = y(c(i.suffix)); - if (i.pattern) { - if ((n && n.push(i), m || v)) { + var i = g[C]; + if (typeof i == 'string') x += s(p(i)); + else { + var R = s(p(i.prefix)), + y = s(p(i.suffix)); + if (i.pattern) + if ((n && n.push(i), R || y)) if (i.modifier === '+' || i.modifier === '*') { - var g = i.modifier === '*' ? '?' : ''; - o += '(?:' - .concat(m, '((?:') + var O = i.modifier === '*' ? '?' : ''; + x += '(?:' + .concat(R, '((?:') .concat(i.pattern, ')(?:') - .concat(v) - .concat(m, '(?:') + .concat(y) + .concat(R, '(?:') .concat(i.pattern, '))*)') - .concat(v, ')') - .concat(g); - } else { - o += '(?:'.concat(m, '(').concat(i.pattern, ')').concat(v, ')').concat(i.modifier); - } - } else { - i.modifier === '+' || i.modifier === '*' - ? (o += '((?:'.concat(i.pattern, ')').concat(i.modifier, ')')) - : (o += '('.concat(i.pattern, ')').concat(i.modifier)); + .concat(y, ')') + .concat(O); + } else x += '(?:'.concat(R, '(').concat(i.pattern, ')').concat(y, ')').concat(i.modifier); + else { + if (i.modifier === '+' || i.modifier === '*') + throw new TypeError('Can not repeat "'.concat(i.name, '" without a prefix and suffix')); + x += '('.concat(i.pattern, ')').concat(i.modifier); } - } else { - o += '(?:'.concat(m).concat(v, ')').concat(i.modifier); - } + else x += '(?:'.concat(R).concat(y, ')').concat(i.modifier); } } - if (d) { - u || (o += ''.concat(x, '?')), (o += e.endsWith ? '(?='.concat(E, ')') : '$'); - } else { - var h = r[r.length - 1], - b = typeof h == 'string' ? x.indexOf(h[h.length - 1]) > -1 : h === void 0; - u || (o += '(?:'.concat(x, '(?=').concat(E, '))?')), b || (o += '(?='.concat(x, '|').concat(E, ')')); + if (m) u || (x += ''.concat(T, '?')), (x += e.endsWith ? '(?='.concat(A, ')') : '$'); + else { + var b = r[r.length - 1], + l = typeof b == 'string' ? T.indexOf(b[b.length - 1]) > -1 : b === void 0; + u || (x += '(?:'.concat(T, '(?=').concat(A, '))?')), l || (x += '(?='.concat(T, '|').concat(A, ')')); } - return new RegExp(o, O(e)); + return new RegExp(x, D(e)); } + function P(r, n, e) { - return r instanceof RegExp ? M(r, n) : Array.isArray(r) ? S(r, n, e) : F(r, n, e); + return r instanceof RegExp ? $(r, n) : Array.isArray(r) ? W(r, n, e) : L(r, n, e); } -export { P as pathToRegexp }; +export { H as match, P as pathToRegexp }; diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts index bf3fc978401..bfe137c0052 100644 --- a/packages/shared/src/constants.ts +++ b/packages/shared/src/constants.ts @@ -16,3 +16,11 @@ export const STAGING_ENV_SUFFIXES = ['.accountsstage.dev']; export const LOCAL_API_URL = 'https://api.lclclerk.com'; export const STAGING_API_URL = 'https://api.clerkstage.dev'; export const PROD_API_URL = 'https://api.clerk.com'; + +/** + * Returns the URL for a static image + * using the new img.clerk.com service + */ +export function iconImageUrl(id: string, format: 'svg' | 'jpeg' = 'svg'): string { + return `https://img.clerk.com/static/${id}.${format}`; +} diff --git a/packages/shared/src/pathToRegexp.ts b/packages/shared/src/pathToRegexp.ts index 64004295659..fcb11a1e4fe 100644 --- a/packages/shared/src/pathToRegexp.ts +++ b/packages/shared/src/pathToRegexp.ts @@ -1,12 +1,36 @@ -import { pathToRegexp as pathToRegexpBase } from './compiled/path-to-regexp'; +import type { + Match, + MatchFunction, + ParseOptions, + Path, + RegexpToFunctionOptions, + TokensToRegexpOptions, +} from './compiled/path-to-regexp'; +import { match as matchBase, pathToRegexp as pathToRegexpBase } from './compiled/path-to-regexp'; export const pathToRegexp = (path: string) => { try { // @ts-ignore no types exists for the pre-compiled package - return pathToRegexpBase(path) as RegExp; + return pathToRegexpBase(path); } catch (e: any) { throw new Error( - `Invalid path: ${path}.\nConsult the documentation of path-to-regexp here: https://github.com/pillarjs/path-to-regexp\n${e.message}`, + `Invalid path: ${path}.\nConsult the documentation of path-to-regexp here: https://github.com/pillarjs/path-to-regexp/tree/6.x\n${e.message}`, ); } }; + +export function match

( + str: Path, + options?: ParseOptions & TokensToRegexpOptions & RegexpToFunctionOptions, +): MatchFunction

{ + try { + // @ts-ignore no types exists for the pre-compiled package + return matchBase(str, options); + } catch (e: any) { + throw new Error( + `Invalid path and options: Consult the documentation of path-to-regexp here: https://github.com/pillarjs/path-to-regexp/tree/6.x\n${e.message}`, + ); + } +} + +export { type Match, type MatchFunction }; diff --git a/packages/shared/src/router/router.ts b/packages/shared/src/router/router.ts index d20e6760b3b..dca2e40946c 100644 --- a/packages/shared/src/router/router.ts +++ b/packages/shared/src/router/router.ts @@ -135,6 +135,14 @@ export function createClerkRouter(router: ClerkHostRouter, basePath: string = '/ return router.shallowPush(destinationUrl); } + function pathname() { + return router.pathname(); + } + + function searchParams() { + return router.searchParams(); + } + return { child, match, @@ -143,8 +151,8 @@ export function createClerkRouter(router: ClerkHostRouter, basePath: string = '/ push, replace, shallowPush, - pathname: router.pathname, - searchParams: router.searchParams, + pathname, + searchParams, basePath: normalizedBasePath, }; } diff --git a/packages/tanstack-start/CHANGELOG.md b/packages/tanstack-start/CHANGELOG.md index 0692b4b6924..6a896993771 100644 --- a/packages/tanstack-start/CHANGELOG.md +++ b/packages/tanstack-start/CHANGELOG.md @@ -1,5 +1,32 @@ # @clerk/tanstack-start +## 0.4.7 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + - @clerk/backend@1.13.4 + - @clerk/clerk-react@5.9.3 + - @clerk/shared@2.8.3 + +## 0.4.6 + +### Patch Changes + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`418be2fdb`](https://github.com/clerk/javascript/commit/418be2fdb558bb5c85d7be491945935b44cad681), [`c59636a1a`](https://github.com/clerk/javascript/commit/c59636a1aca67be7d6732d281cec307ed456678b), [`5c18671f1`](https://github.com/clerk/javascript/commit/5c18671f158f8077f822877ce5c1fa192199aeda), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529), [`e0ca9dc94`](https://github.com/clerk/javascript/commit/e0ca9dc94fa68f3d3db5d2433fa6b85d800d4ca2)]: + - @clerk/shared@2.8.2 + - @clerk/clerk-react@5.9.2 + - @clerk/types@4.21.1 + - @clerk/backend@1.13.3 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies [[`02babaccb`](https://github.com/clerk/javascript/commit/02babaccb648fa4e22f38cc0f572d44f82b09f78)]: + - @clerk/backend@1.13.2 + ## 0.4.4 ### Patch Changes diff --git a/packages/tanstack-start/package.json b/packages/tanstack-start/package.json index 16c82a756d8..dc214ffd330 100644 --- a/packages/tanstack-start/package.json +++ b/packages/tanstack-start/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/tanstack-start", - "version": "0.4.4", + "version": "0.4.7", "description": "Clerk SDK for TanStack Start", "keywords": [ "clerk", @@ -54,10 +54,10 @@ "publish:local": "npx yalc push --replace --sig" }, "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/clerk-react": "5.9.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/clerk-react": "5.9.3", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "tslib": "2.4.1" }, "devDependencies": { diff --git a/packages/testing/CHANGELOG.md b/packages/testing/CHANGELOG.md index df9293621f0..d04f722d559 100644 --- a/packages/testing/CHANGELOG.md +++ b/packages/testing/CHANGELOG.md @@ -1,5 +1,30 @@ # @clerk/testing +## 1.3.5 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + - @clerk/backend@1.13.4 + - @clerk/shared@2.8.3 + +## 1.3.4 + +### Patch Changes + +- Updated dependencies [[`cb32aaf59`](https://github.com/clerk/javascript/commit/cb32aaf59d38dcd12e959f542782f71a87adf9c1), [`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7), [`6275c242c`](https://github.com/clerk/javascript/commit/6275c242cd8bcb6f7766934059967e0fe775a0c1), [`418be2fdb`](https://github.com/clerk/javascript/commit/418be2fdb558bb5c85d7be491945935b44cad681), [`c59636a1a`](https://github.com/clerk/javascript/commit/c59636a1aca67be7d6732d281cec307ed456678b), [`5c18671f1`](https://github.com/clerk/javascript/commit/5c18671f158f8077f822877ce5c1fa192199aeda), [`f9faaf031`](https://github.com/clerk/javascript/commit/f9faaf03100baf679c78e6c24877fbf3b60be529), [`e0ca9dc94`](https://github.com/clerk/javascript/commit/e0ca9dc94fa68f3d3db5d2433fa6b85d800d4ca2)]: + - @clerk/shared@2.8.2 + - @clerk/types@4.21.1 + - @clerk/backend@1.13.3 + +## 1.3.3 + +### Patch Changes + +- Updated dependencies [[`02babaccb`](https://github.com/clerk/javascript/commit/02babaccb648fa4e22f38cc0f572d44f82b09f78)]: + - @clerk/backend@1.13.2 + ## 1.3.2 ### Patch Changes diff --git a/packages/testing/package.json b/packages/testing/package.json index db0eae69da6..479d000ed95 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/testing", - "version": "1.3.2", + "version": "1.3.5", "description": "Utilities to help you create E2E test suites for apps using Clerk", "keywords": [ "auth", @@ -62,9 +62,9 @@ "lint": "eslint src/" }, "dependencies": { - "@clerk/backend": "1.13.1", - "@clerk/shared": "2.8.1", - "@clerk/types": "4.21.0", + "@clerk/backend": "1.13.4", + "@clerk/shared": "2.8.3", + "@clerk/types": "4.22.0", "dotenv": "16.4.5" }, "devDependencies": { diff --git a/packages/themes/CHANGELOG.md b/packages/themes/CHANGELOG.md index 51d1686ddcc..9a5269176a0 100644 --- a/packages/themes/CHANGELOG.md +++ b/packages/themes/CHANGELOG.md @@ -1,5 +1,19 @@ # Change Log +## 2.1.32 + +### Patch Changes + +- Updated dependencies [[`c9063853e`](https://github.com/clerk/javascript/commit/c9063853e538a4010f5d4e522a3da5abc80098a4), [`19d3808d4`](https://github.com/clerk/javascript/commit/19d3808d4672234944226d6709ec51214e8d6e1d), [`737bcbb0f`](https://github.com/clerk/javascript/commit/737bcbb0ffb5e2dcadbb02e8fc718fe8825c5842)]: + - @clerk/types@4.22.0 + +## 2.1.31 + +### Patch Changes + +- Updated dependencies [[`2e5c550e4`](https://github.com/clerk/javascript/commit/2e5c550e4aec61150c2a17fdcd4a0e1273cb50e7)]: + - @clerk/types@4.21.1 + ## 2.1.30 ### Patch Changes diff --git a/packages/themes/package.json b/packages/themes/package.json index 4675f565cb3..b51cdcc08c9 100644 --- a/packages/themes/package.json +++ b/packages/themes/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/themes", - "version": "2.1.30", + "version": "2.1.32", "description": "Themes for the Clerk auth components", "keywords": [ "react", @@ -37,7 +37,7 @@ "lint": "eslint src/" }, "dependencies": { - "@clerk/types": "4.21.0", + "@clerk/types": "4.22.0", "tslib": "2.4.1" }, "devDependencies": { diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index beeb987202c..50dd567b3e7 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## 4.22.0 + +### Minor Changes + +- Hide sign up url from `` component when mode is `restricted` ([#4206](https://github.com/clerk/javascript/pull/4206)) by [@nikospapcom](https://github.com/nikospapcom) + +### Patch Changes + +- Supports default role on `OrganizationProfile` invitations. When inviting a member, the default role will be automatically selected, otherwise it falls back to the only available role. ([#4210](https://github.com/clerk/javascript/pull/4210)) by [@LauraBeatris](https://github.com/LauraBeatris) + +- Add type for \_\_internal_country ([#4215](https://github.com/clerk/javascript/pull/4215)) by [@dstaley](https://github.com/dstaley) + +## 4.21.1 + +### Patch Changes + +- Improve JSDoc comments for some public API properties ([#4190](https://github.com/clerk/javascript/pull/4190)) by [@LekoArts](https://github.com/LekoArts) + ## 4.21.0 ### Minor Changes diff --git a/packages/types/package.json b/packages/types/package.json index 57ca5a56fac..f30f621c0c4 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/types", - "version": "4.21.0", + "version": "4.22.0", "description": "Typings for Clerk libraries.", "keywords": [ "clerk", diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 76a42153f0f..a9317f87b58 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -137,6 +137,8 @@ export interface Clerk { telemetry: TelemetryCollector | undefined; + __internal_country?: string | null; + /** * Signs out the current user on single-session instances, or all users on multi-session instances * @param signOutCallback - Optional A callback that runs after sign out completes. @@ -655,13 +657,21 @@ export type ClerkOptions = ClerkOptionsNavigation & */ localization?: LocalizationResource; polling?: boolean; + /** + * By default, the last active session is used during client initialization. This option allows you to override that behavior, e.g. by selecting a specific session. + */ selectInitialSession?: (client: ClientResource) => ActiveSessionResource | null; - /** Controls if ClerkJS will load with the standard browser setup using Clerk cookies */ + /** + * By default, ClerkJS is loaded with the assumption that cookies can be set (browser setup). On native platforms this value must be set to `false`. + */ standardBrowser?: boolean; /** * Optional support email for display in authentication screens. Will only affect [Clerk Components](https://clerk.com/docs/components/overview) and not [Account Portal](https://clerk.com/docs/customization/account-portal/overview) pages. */ supportEmail?: string; + /** + * By default, the [FAPI `touch` endpoint](https://clerk.com/docs/reference/frontend-api/tag/Sessions#operation/touchSession) is called during page focus to keep the last active session alive. This option allows you to disable this behavior. + */ touchSession?: boolean; /** * This URL will be used for any redirects that might happen and needs to point to your primary application on the client-side. This option is optional for production instances. It's required for development instances if you a use satellite application. diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 547ac4ae763..482d807e42d 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -59,3 +59,4 @@ export * from './customPages'; export * from './pagination'; export * from './passkey'; export * from './customMenuItems'; +export * from './samlConnection'; diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts index f01d00870df..88a07f00770 100644 --- a/packages/types/src/json.ts +++ b/packages/types/src/json.ts @@ -199,6 +199,7 @@ export interface SamlAccountJSON extends ClerkResourceJSON { first_name: string; last_name: string; verification?: VerificationJSON; + saml_connection?: SamlAccountConnectionJSON; } export interface UserJSON extends ClerkResourceJSON { @@ -352,7 +353,7 @@ export interface OrganizationInvitationJSON extends ClerkResourceJSON { updated_at: number; } -interface OrganizationDomainVerificationJSON { +export interface OrganizationDomainVerificationJSON { status: OrganizationDomainVerificationStatus; strategy: 'email_code'; // only available value for now attempts: number; @@ -512,3 +513,17 @@ export interface PublicKeyCredentialRequestOptionsJSON { timeout: number; userVerification: 'discouraged' | 'preferred' | 'required'; } + +export interface SamlAccountConnectionJSON extends ClerkResourceJSON { + id: string; + name: string; + domain: string; + active: boolean; + provider: string; + sync_user_attributes: boolean; + allow_subdomains: boolean; + allow_idp_initiated: boolean; + disable_additional_identifications: boolean; + created_at: number; + updated_at: number; +} diff --git a/packages/types/src/organizationSettings.ts b/packages/types/src/organizationSettings.ts index 93c125af4c6..68743ea3713 100644 --- a/packages/types/src/organizationSettings.ts +++ b/packages/types/src/organizationSettings.ts @@ -13,6 +13,7 @@ export interface OrganizationSettingsJSON extends ClerkResourceJSON { domains: { enabled: boolean; enrollment_modes: OrganizationEnrollmentMode[]; + default_role: string | null; }; } @@ -25,5 +26,6 @@ export interface OrganizationSettingsResource extends ClerkResource { domains: { enabled: boolean; enrollmentModes: OrganizationEnrollmentMode[]; + defaultRole: string | null; }; } diff --git a/packages/types/src/samlAccount.ts b/packages/types/src/samlAccount.ts index 5a1fff291c6..432bbd53d3c 100644 --- a/packages/types/src/samlAccount.ts +++ b/packages/types/src/samlAccount.ts @@ -1,5 +1,6 @@ import type { ClerkResource } from './resource'; import type { SamlIdpSlug } from './saml'; +import type { SamlAccountConnectionResource } from './samlConnection'; import type { VerificationResource } from './verification'; export interface SamlAccountResource extends ClerkResource { @@ -10,4 +11,5 @@ export interface SamlAccountResource extends ClerkResource { firstName: string; lastName: string; verification: VerificationResource | null; + samlConnection: SamlAccountConnectionResource | null; } diff --git a/packages/types/src/samlConnection.ts b/packages/types/src/samlConnection.ts new file mode 100644 index 00000000000..f3b693e3fbf --- /dev/null +++ b/packages/types/src/samlConnection.ts @@ -0,0 +1,15 @@ +import type { ClerkResource } from './resource'; + +export interface SamlAccountConnectionResource extends ClerkResource { + id: string; + name: string; + domain: string; + active: boolean; + provider: string; + syncUserAttributes: boolean; + allowSubdomains: boolean; + allowIdpInitiated: boolean; + disableAdditionalIdentifications: boolean; + createdAt: Date; + updatedAt: Date; +} diff --git a/packages/types/src/userSettings.ts b/packages/types/src/userSettings.ts index ca61c419537..9edc2755e70 100644 --- a/packages/types/src/userSettings.ts +++ b/packages/types/src/userSettings.ts @@ -47,10 +47,13 @@ export type SignInData = { }; }; +export type SignUpModes = 'public' | 'restricted'; + export type SignUpData = { allowlist_only: boolean; progressive: boolean; captcha_enabled: boolean; + mode: SignUpModes; }; export type PasswordSettingsData = { diff --git a/packages/ui/src/common/connections.tsx b/packages/ui/src/common/connections.tsx index 9928d8d51f2..92bca495068 100644 --- a/packages/ui/src/common/connections.tsx +++ b/packages/ui/src/common/connections.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { useAppearance } from '~/contexts'; import { useEnabledConnections } from '~/hooks/use-enabled-connections'; +import { useLocalizations } from '~/hooks/use-localizations'; import { Button } from '~/primitives/button'; import { PROVIDERS } from '~/primitives/icons/providers'; @@ -56,51 +57,54 @@ function getColumnCount({ length, max }: Record<'length' | 'max', number>): numb export function Connections( props: { columns?: number } & Pick, 'disabled' | 'textVisuallyHidden'>, ) { + const { t } = useLocalizations(); const enabledConnections = useEnabledConnections(); - const { layout } = useAppearance().parsedAppearance; + const { options } = useAppearance().parsedAppearance; const hasConnection = enabledConnections.length > 0; const textVisuallyHidden = typeof props?.textVisuallyHidden !== 'undefined' ? props.textVisuallyHidden - : enabledConnections.length > 2 || layout?.socialButtonsVariant === 'iconButton'; + : enabledConnections.length > 2 || options?.socialButtonsVariant === 'iconButton'; const columns = getColumnCount({ length: enabledConnections.length, max: props?.columns || 6 }); + const localizationKey = + enabledConnections.length === 1 ? 'socialButtonsBlockButton' : 'socialButtonsBlockButtonManyInView'; return hasConnection ? ( -

-
    - {enabledConnections.map(c => { - return ( -
  • - - {isConnectionLoading => { - return ( - + {enabledConnections.map(c => { + return ( +
  • + + {isConnectionLoading => { + return ( + + - - ); - }} - -
  • - ); - })} -
-
+ {t(localizationKey, { + provider: c.name, + })} + + + ); + }} + + + ); + })} + ) : null; } diff --git a/packages/ui/src/common/email-field.tsx b/packages/ui/src/common/email-field.tsx index 9bb7c7fddbf..77f49073564 100644 --- a/packages/ui/src/common/email-field.tsx +++ b/packages/ui/src/common/email-field.tsx @@ -8,16 +8,19 @@ import * as Field from '~/primitives/field'; const DEFAULT_FIELD_NAME = 'emailAddress'; -export function EmailField({ - alternativeFieldTrigger, - name = DEFAULT_FIELD_NAME, - enabled, - required, - ...props -}: { - alternativeFieldTrigger?: React.ReactNode; - enabled?: boolean; -} & Omit, 'type'>) { +export const EmailField = React.forwardRef(function EmailField( + { + alternativeFieldTrigger, + name = DEFAULT_FIELD_NAME, + enabled, + required, + ...props + }: { + alternativeFieldTrigger?: React.ReactNode; + enabled?: boolean; + } & Omit, 'type'>, + forwardedRef: React.ForwardedRef, +) { const { t, translateError } = useLocalizations(); const { enabled: attributeEnabled, required: attributeRequired } = useAttributes('email_address'); @@ -48,6 +51,7 @@ export function EmailField({ {({ state }) => { return ( ); -} +}); diff --git a/packages/ui/src/common/email-or-phone-number-field.tsx b/packages/ui/src/common/email-or-phone-number-field.tsx index 7e89b83c701..d643a7e51a5 100644 --- a/packages/ui/src/common/email-or-phone-number-field.tsx +++ b/packages/ui/src/common/email-or-phone-number-field.tsx @@ -2,6 +2,7 @@ import type * as Common from '@clerk/elements/common'; import * as React from 'react'; import { ToggleButton } from 'react-aria-components'; +import { useFocusInput } from '~/hooks/use-focus-input'; import { link } from '~/primitives/link'; import { EmailField } from './email-field'; @@ -25,10 +26,15 @@ export function EmailOrPhoneNumberField({ } & Omit, 'type'>) { const [showPhoneNumberField, setShowPhoneNumberField] = React.useState(false); + const [inputRef, setInputFocus] = useFocusInput(); + const toggle = ( { + setShowPhoneNumberField(isSelected); + setInputFocus(); + }} className={link({ size: 'sm', disabled: props.disabled })} > {showPhoneNumberField ? toggleLabelEmail : toggleLabelPhoneNumber} @@ -40,12 +46,14 @@ export function EmailOrPhoneNumberField({ alternativeFieldTrigger={toggle} name={name} {...props} + ref={inputRef} /> ) : ( ); } diff --git a/packages/ui/src/common/email-or-username-field.tsx b/packages/ui/src/common/email-or-username-field.tsx index a65bb3fd1c8..21641e28b04 100644 --- a/packages/ui/src/common/email-or-username-field.tsx +++ b/packages/ui/src/common/email-or-username-field.tsx @@ -5,13 +5,16 @@ import { useLocalizations } from '~/hooks/use-localizations'; import { Animated } from '~/primitives/animated'; import * as Field from '~/primitives/field'; -export function EmailOrUsernameField({ - alternativeFieldTrigger, +export const EmailOrUsernameField = React.forwardRef(function EmailOrUsernameField( + { + alternativeFieldTrigger, - ...props -}: { - alternativeFieldTrigger?: React.ReactNode; -} & Omit, 'type'>) { + ...props + }: { + alternativeFieldTrigger?: React.ReactNode; + } & Omit, 'type'>, + forwardedRef: React.ForwardedRef, +) { const { t } = useLocalizations(); return ( @@ -30,6 +33,7 @@ export function EmailOrUsernameField({ {({ state }) => { return ( ); -} +}); diff --git a/packages/ui/src/common/email-or-username-or-phone-number-field.tsx b/packages/ui/src/common/email-or-username-or-phone-number-field.tsx index 71ec269b671..9b129a77c58 100644 --- a/packages/ui/src/common/email-or-username-or-phone-number-field.tsx +++ b/packages/ui/src/common/email-or-username-or-phone-number-field.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { ToggleButton } from 'react-aria-components'; import { LOCALIZATION_NEEDED } from '~/constants/localizations'; +import { useFocusInput } from '~/hooks/use-focus-input'; import { link } from '~/primitives/link'; import { EmailOrUsernameField } from './email-or-username-field'; @@ -26,10 +27,15 @@ export function EmailOrUsernameOrPhoneNumberField({ } & Omit, 'type'>) { const [showPhoneNumberField, setShowPhoneNumberField] = React.useState(false); + const [inputRef, setInputFocus] = useFocusInput(); + const toggle = ( { + setShowPhoneNumberField(isSelected); + setInputFocus(); + }} className={link({ size: 'sm', disabled: props.disabled, focusVisible: 'data-attribute' })} > {LOCALIZATION_NEEDED.formFieldAccessibleLabel__emailOrUsernameOrPhone} @@ -42,12 +48,14 @@ export function EmailOrUsernameOrPhoneNumberField({ name={name} alternativeFieldTrigger={toggle} {...props} + ref={inputRef} /> ) : ( ); } diff --git a/packages/ui/src/common/phone-number-field.tsx b/packages/ui/src/common/phone-number-field.tsx index 41cd3fa4b00..c94884c73c2 100644 --- a/packages/ui/src/common/phone-number-field.tsx +++ b/packages/ui/src/common/phone-number-field.tsx @@ -6,6 +6,7 @@ import * as React from 'react'; import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components'; import { type CountryIso, IsoToCountryMap } from '~/constants/phone-number'; +import { useFocusInput } from '~/hooks/use-focus-input'; import { useLocalizations } from '~/hooks/use-localizations'; import { Animated } from '~/primitives/animated'; import * as Field from '~/primitives/field'; @@ -16,7 +17,7 @@ import { extractDigits, formatPhoneNumber, parsePhoneString } from '~/utils/phon type UseFormattedPhoneNumberProps = { initPhoneWithCode: string; - locationBasedCountryIso?: CountryIso; + locationBasedCountryIso?: CountryIso | null; }; const format = (str: string, iso: CountryIso) => { @@ -92,17 +93,16 @@ export const PhoneNumberField = React.forwardRef(function PhoneNumberField( forwardedRef: React.ForwardedRef, ) { const clerk = useClerk(); - // TODO to fix IsomorphicClerk - const locationBasedCountryIso = (clerk as any)?.clerkjs.__internal_country; + const locationBasedCountryIso = clerk.__internal_country as UseFormattedPhoneNumberProps['locationBasedCountryIso']; const { t, translateError } = useLocalizations(); const [isOpen, setOpen] = React.useState(false); const [selectedCountry, setSelectedCountry] = React.useState(countryOptions[0]); const id = React.useId(); const containerRef = React.useRef(null); - const inputRef = React.useRef(null); const commandListRef = React.useRef(null); const commandInputRef = React.useRef(null); const contentWidth = containerRef.current?.getBoundingClientRect()?.width || 0; + const [inputRef, setInputFocus] = useFocusInput(); const { setNumber, setIso, setNumberAndIso, numberWithCode, formattedNumber, iso } = useFormattedPhoneNumber({ initPhoneWithCode, locationBasedCountryIso, @@ -243,7 +243,7 @@ export const PhoneNumberField = React.forwardRef(function PhoneNumberField( No countries found @@ -255,6 +255,7 @@ export const PhoneNumberField = React.forwardRef(function PhoneNumberField( onSelect={() => { setIso(iso); setOpen(false); + setInputFocus(); }} data-checked={selectedCountry === countryOptions[index]} className='leading-small aria-selected:bg-gray-2 flex cursor-pointer gap-x-2 px-4 py-1.5 text-base' @@ -262,7 +263,7 @@ export const PhoneNumberField = React.forwardRef(function PhoneNumberField( {selectedCountry === countryOptions[index] && } - {name} + {name} +{code} ); @@ -277,7 +278,7 @@ export const PhoneNumberField = React.forwardRef(function PhoneNumberField( // Prevent tab stop tabIndex={-1} className='supports-ios:text-[length:1rem] grid cursor-text place-content-center bg-white px-1 text-base' - onClick={() => inputRef.current?.focus()} + onClick={() => setInputFocus()} disabled={props.disabled} > +{selectedCountry.code} diff --git a/packages/ui/src/components/sign-in/steps/get-help.tsx b/packages/ui/src/components/sign-in/steps/get-help.tsx index 96c72604269..3498f9ba3ee 100644 --- a/packages/ui/src/components/sign-in/steps/get-help.tsx +++ b/packages/ui/src/components/sign-in/steps/get-help.tsx @@ -1,8 +1,7 @@ import { useGetHelp } from '~/components/sign-in/hooks/use-get-help'; import { LOCALIZATION_NEEDED } from '~/constants/localizations'; -import { useAppearance } from '~/contexts'; +import { useCard } from '~/hooks/use-card'; import { useDevModeWarning } from '~/hooks/use-dev-mode-warning'; -import { useDisplayConfig } from '~/hooks/use-display-config'; import { useLocalizations } from '~/hooks/use-localizations'; import { useSupportEmail } from '~/hooks/use-support-email'; import { Button } from '~/primitives/button'; @@ -12,29 +11,16 @@ import { LinkButton } from '~/primitives/link'; export function SignInGetHelp() { const { t } = useLocalizations(); - const { applicationName, branded, logoImageUrl, homeUrl } = useDisplayConfig(); - const { layout } = useAppearance().parsedAppearance; const isDev = useDevModeWarning(); const supportEmail = useSupportEmail(); const { setShowHelp } = useGetHelp(); - - const cardLogoProps = { - href: layout?.logoLinkUrl || homeUrl, - src: layout?.logoImageUrl || logoImageUrl, - alt: applicationName, - }; - const cardFooterProps = { - branded, - helpPageUrl: layout?.helpPageUrl, - privacyPageUrl: layout?.privacyPageUrl, - termsPageUrl: layout?.termsPageUrl, - }; + const { logoProps, footerProps } = useCard(); return ( - + {t('signIn.alternativeMethods.getHelp.title')} {t('signIn.alternativeMethods.getHelp.content')} @@ -51,7 +37,7 @@ export function SignInGetHelp() { setShowHelp(false)}>{t('backButton')} - + ); } diff --git a/packages/ui/src/components/sign-in/steps/start.tsx b/packages/ui/src/components/sign-in/steps/start.tsx index 6c0d18ebd5d..a37958b4b45 100644 --- a/packages/ui/src/components/sign-in/steps/start.tsx +++ b/packages/ui/src/components/sign-in/steps/start.tsx @@ -11,12 +11,14 @@ import { PhoneNumberField } from '~/common/phone-number-field'; import { PhoneNumberOrUsernameField } from '~/common/phone-number-or-username-field'; import { UsernameField } from '~/common/username-field'; import { LOCALIZATION_NEEDED } from '~/constants/localizations'; +import { SIGN_UP_MODES } from '~/constants/user-settings'; import { useAppearance } from '~/contexts'; import { useAttributes } from '~/hooks/use-attributes'; import { useCard } from '~/hooks/use-card'; import { useDevModeWarning } from '~/hooks/use-dev-mode-warning'; import { useDisplayConfig } from '~/hooks/use-display-config'; import { useEnabledConnections } from '~/hooks/use-enabled-connections'; +import { useEnvironment } from '~/hooks/use-environment'; import { useLocalizations } from '~/hooks/use-localizations'; import { Button } from '~/primitives/button'; import * as Card from '~/primitives/card'; @@ -27,6 +29,7 @@ import { Separator } from '~/primitives/separator'; export function SignInStart() { const enabledConnections = useEnabledConnections(); const { t } = useLocalizations(); + const { userSettings } = useEnvironment(); const { enabled: usernameEnabled } = useAttributes('username'); const { enabled: phoneNumberEnabled } = useAttributes('phone_number'); const { enabled: emailAddressEnabled } = useAttributes('email_address'); @@ -36,7 +39,7 @@ export function SignInStart() { const hasConnection = enabledConnections.length > 0; const hasIdentifier = emailAddressEnabled || usernameEnabled || phoneNumberEnabled; const isDev = useDevModeWarning(); - const { layout } = useAppearance().parsedAppearance; + const { options } = useAppearance().parsedAppearance; const { logoProps, footerProps } = useCard(); return ( @@ -68,7 +71,7 @@ export function SignInStart() { - {layout.socialButtonsPlacement === 'top' ? connectionsWithSeperator : null} + {options.socialButtonsPlacement === 'top' ? connectionsWithSeperator : null} {hasIdentifier ? (
@@ -135,7 +138,7 @@ export function SignInStart() { ) : null}
) : null} - {layout.socialButtonsPlacement === 'bottom' ? connectionsWithSeperator.reverse() : null} + {options.socialButtonsPlacement === 'bottom' ? connectionsWithSeperator.reverse() : null}
@@ -184,12 +187,14 @@ export function SignInStart() { - - - {t('signIn.start.actionText')}{' '} - {t('signIn.start.actionLink')} - - + {userSettings.signUp.mode === SIGN_UP_MODES.PUBLIC ? ( + + + {t('signIn.start.actionText')}{' '} + {t('signIn.start.actionLink')} + + + ) : null} diff --git a/packages/ui/src/components/sign-in/steps/status.tsx b/packages/ui/src/components/sign-in/steps/status.tsx index be6f9ba1466..6fc7e6ecb45 100644 --- a/packages/ui/src/components/sign-in/steps/status.tsx +++ b/packages/ui/src/components/sign-in/steps/status.tsx @@ -1,9 +1,8 @@ import * as React from 'react'; import { LOCALIZATION_NEEDED } from '~/constants/localizations'; -import { useAppearance } from '~/contexts'; +import { useCard } from '~/hooks/use-card'; import { useDevModeWarning } from '~/hooks/use-dev-mode-warning'; -import { useDisplayConfig } from '~/hooks/use-display-config'; import { useLocalizations } from '~/hooks/use-localizations'; import * as Card from '~/primitives/card'; import ExclamationTrianglelg from '~/primitives/icons/exclamation-triangle-lg'; @@ -59,17 +58,9 @@ const statusIcon: Record = { export function SignInStatus() { const { t } = useLocalizations(); - const { branded } = useDisplayConfig(); - const { layout } = useAppearance().parsedAppearance; const isDev = useDevModeWarning(); const [status] = React.useState('loading'); - - const cardFooterProps = { - branded, - helpPageUrl: layout?.helpPageUrl, - privacyPageUrl: layout?.privacyPageUrl, - termsPageUrl: layout?.termsPageUrl, - }; + const { footerProps } = useCard(); return ( @@ -85,7 +76,7 @@ export function SignInStatus() { ) : null} - + ); } diff --git a/packages/ui/src/components/sign-in/steps/verifications.tsx b/packages/ui/src/components/sign-in/steps/verifications.tsx index 3c183f7e5aa..2f718a9ff3d 100644 --- a/packages/ui/src/components/sign-in/steps/verifications.tsx +++ b/packages/ui/src/components/sign-in/steps/verifications.tsx @@ -67,6 +67,8 @@ export function SignInVerifications() { 0; const hasIdentifier = emailAddressEnabled || usernameEnabled || phoneNumberEnabled; const isDev = useDevModeWarning(); - const { layout } = useAppearance().parsedAppearance; + const { options } = useAppearance().parsedAppearance; const { logoProps, footerProps } = useCard(); return ( @@ -77,11 +85,11 @@ export function SignUpStart() { - {layout.socialButtonsPlacement === 'top' ? connectionsWithSeperator : null} + {options.socialButtonsPlacement === 'top' ? connectionsWithSeperator : null} {hasIdentifier ? (
- {firstNameEnabled && lastNameEnabled ? ( + {showFirstName && showLastName ? (
) : null} - {usernameEnabled ? ( + {showUserName ? ( ) : ( <> - + {showEmailAddress ? : null} - {phoneNumberEnabled ? ( + {showPhoneNumber ? ( )} - {passwordEnabled && passwordRequired ? ( + {showPassword ? ( ) : null} - {layout.socialButtonsPlacement === 'bottom' ? connectionsWithSeperator.reverse() : null} + {options.socialButtonsPlacement === 'bottom' ? connectionsWithSeperator.reverse() : null} {userSettings.signUp.captcha_enabled ? : null} diff --git a/packages/ui/src/components/sign-up/steps/status.tsx b/packages/ui/src/components/sign-up/steps/status.tsx index 13ae0c4c483..5e1264b2be4 100644 --- a/packages/ui/src/components/sign-up/steps/status.tsx +++ b/packages/ui/src/components/sign-up/steps/status.tsx @@ -1,9 +1,8 @@ import * as React from 'react'; import { LOCALIZATION_NEEDED } from '~/constants/localizations'; -import { useAppearance } from '~/contexts'; +import { useCard } from '~/hooks/use-card'; import { useDevModeWarning } from '~/hooks/use-dev-mode-warning'; -import { useDisplayConfig } from '~/hooks/use-display-config'; import { useLocalizations } from '~/hooks/use-localizations'; import * as Card from '~/primitives/card'; import ExclamationTrianglelg from '~/primitives/icons/exclamation-triangle-lg'; @@ -59,17 +58,9 @@ const statusIcon: Record = { export function SignUpStatus() { const { t } = useLocalizations(); - const { branded } = useDisplayConfig(); - const { layout } = useAppearance().parsedAppearance; const isDev = useDevModeWarning(); const [status] = React.useState('loading'); - - const cardFooterProps = { - branded, - helpPageUrl: layout?.helpPageUrl, - privacyPageUrl: layout?.privacyPageUrl, - termsPageUrl: layout?.termsPageUrl, - }; + const { footerProps } = useCard(); return ( @@ -85,7 +76,7 @@ export function SignUpStatus() { ) : null} - + ); } diff --git a/packages/ui/src/constants/user-settings.ts b/packages/ui/src/constants/user-settings.ts new file mode 100644 index 00000000000..54db661f8b1 --- /dev/null +++ b/packages/ui/src/constants/user-settings.ts @@ -0,0 +1,6 @@ +import type { SignUpModes } from '@clerk/types'; + +export const SIGN_UP_MODES: Record = { + PUBLIC: 'public', + RESTRICTED: 'restricted', +}; diff --git a/packages/ui/src/contexts/AppearanceContext.test.tsx b/packages/ui/src/contexts/AppearanceContext.test.tsx index 1bcf095e981..8ee4c18508d 100644 --- a/packages/ui/src/contexts/AppearanceContext.test.tsx +++ b/packages/ui/src/contexts/AppearanceContext.test.tsx @@ -13,7 +13,7 @@ describe('AppearanceContext', () => { const { result } = renderHook(useAppearance, { wrapper }); expect(result.current).toStrictEqual({ parsedAppearance: { - layout: defaultAppearance.layout, + options: defaultAppearance.options, elements: fullTheme, theme: fullTheme, }, @@ -30,7 +30,7 @@ describe('AppearanceContext', () => { const { result } = renderHook(useAppearance, { wrapper }); expect(result.current).toStrictEqual({ parsedAppearance: { - layout: defaultAppearance.layout, + options: defaultAppearance.options, elements: { ...fullTheme, alert__warning: { @@ -60,7 +60,7 @@ describe('AppearanceContext', () => { const { result } = renderHook(useAppearance, { wrapper }); expect(result.current).toStrictEqual({ parsedAppearance: { - layout: defaultAppearance.layout, + options: defaultAppearance.options, elements: { ...fullTheme, alert__warning: { @@ -76,7 +76,7 @@ describe('AppearanceContext', () => { elements: { alert__warning: 'class-two class-three', }, - layout: {}, + options: {}, }, }); }); @@ -93,7 +93,7 @@ describe('AppearanceContext', () => { const { result } = renderHook(useAppearance, { wrapper }); expect(result.current).toStrictEqual({ parsedAppearance: { - layout: defaultAppearance.layout, + options: defaultAppearance.options, elements: { ...fullTheme, alert__warning: { @@ -111,7 +111,7 @@ describe('AppearanceContext', () => { background: 'red', }, }, - layout: {}, + options: {}, }, }); }); diff --git a/packages/ui/src/contexts/AppearanceContext.tsx b/packages/ui/src/contexts/AppearanceContext.tsx index 15a3220cbfb..90c1882173e 100644 --- a/packages/ui/src/contexts/AppearanceContext.tsx +++ b/packages/ui/src/contexts/AppearanceContext.tsx @@ -1,5 +1,5 @@ import { createContextAndHook, useDeepEqualMemo } from '@clerk/shared/react'; -import type { Appearance as CurrentAppearance, Layout } from '@clerk/types'; +import type { Appearance as CurrentAppearance, Layout as CurrentLayout } from '@clerk/types'; import React from 'react'; import { fullTheme } from '~/themes'; @@ -37,13 +37,16 @@ export type ParsedElementsFragment = Partial; * the main type interacted with within components. */ export type ParsedElements = Record; -export type ParsedLayout = Required; +export type ParsedOptions = Omit & { + logoVisibility?: 'visible' | 'hidden'; +}; type ElementsAppearanceConfig = string | (React.CSSProperties & { className?: string }); -export type Appearance = Omit & { +export type Appearance = Omit & { theme?: ParsedElements; elements?: Partial>; + options?: ParsedOptions; }; export type AppearanceCascade = { @@ -58,7 +61,7 @@ export type AppearanceCascade = { export type ParsedAppearance = { theme: ParsedElements; elements: ParsedElements; - layout: ParsedLayout; + options: ParsedOptions; }; type AppearanceContextValue = { @@ -68,8 +71,8 @@ type AppearanceContextValue = { * Example: * ```tsx * function Help() { - * const { layout } = useAppearance().parsedAppearance; - * return

{layout.helpPageUrl}

+ * const { options } = useAppearance().parsedAppearance; + * return

{options.helpPageUrl}

* } * ``` */ @@ -79,7 +82,7 @@ type AppearanceContextValue = { }; /** - * Used to merge full themes with ParsedElementsFragments. Allows you to combine layoutStyle with multiple visualStyle + * Used to merge full themes with ParsedElementsFragments. Allows you to combine optionsStyle with multiple visualStyle * elements. */ export function mergeParsedElementsFragment(...fragments: ParsedElementsFragment[]): ParsedElementsFragment { @@ -164,7 +167,7 @@ function mergeAppearance(a: Appearance | null | undefined, b: Appearance | null return a; } - const result = { ...a, layout: { ...a.layout, ...b.layout } }; + const result = { ...a, options: { ...a.options, ...b.options } }; // Ensure options are merged if (b.theme) { result.theme = b.theme; @@ -189,7 +192,7 @@ function mergeAppearance(a: Appearance | null | undefined, b: Appearance | null function applyTheme(theme: ParsedElements | undefined, appearance: Appearance | null): ParsedAppearance { const baseTheme = theme ?? fullTheme; if (!appearance) { - return { theme: baseTheme, elements: structuredClone(baseTheme), layout: defaultAppearance.layout }; + return { theme: baseTheme, elements: structuredClone(baseTheme), options: defaultAppearance.options }; } const result = { @@ -197,7 +200,7 @@ function applyTheme(theme: ParsedElements | undefined, appearance: Appearance | // because we're going to perform modifications to deeply nested objects, we need to create a structuredClone of // the theme or else subsequent usage of the baseTheme will contain our merged changes. elements: structuredClone(baseTheme), - layout: { ...defaultAppearance.layout, ...appearance.layout }, + options: { ...defaultAppearance.options, ...appearance.options }, }; if (appearance.elements) { @@ -223,8 +226,8 @@ function applyTheme(theme: ParsedElements | undefined, appearance: Appearance | export const defaultAppearance: ParsedAppearance = { theme: fullTheme, elements: fullTheme, - layout: { - logoPlacement: 'inside', + options: { + logoVisibility: 'visible', socialButtonsPlacement: 'top', socialButtonsVariant: 'auto', logoImageUrl: '', @@ -280,7 +283,7 @@ if (import.meta.vitest) { const a = { elements: { alert__warning: 'cl-test-class-one' } }; const b = { elements: { alertIcon: 'cl-test-class-two' } }; expect(mergeAppearance(a, b)).toStrictEqual({ - layout: {}, + options: {}, elements: { alert__warning: 'cl-test-class-one', alertIcon: 'cl-test-class-two', @@ -294,7 +297,7 @@ if (import.meta.vitest) { elements: { alertIcon: 'cl-test-class-two' }, }; expect(mergeAppearance(a, b)).toStrictEqual({ - layout: {}, + options: {}, theme: a.theme, elements: { alert__warning: 'cl-test-class-one', @@ -310,7 +313,7 @@ if (import.meta.vitest) { elements: { alertIcon: 'cl-test-class-two' }, }; expect(mergeAppearance(a, b)).toStrictEqual({ - layout: {}, + options: {}, theme: b.theme, elements: { alert__warning: 'cl-test-class-one', @@ -323,7 +326,7 @@ if (import.meta.vitest) { const a = { elements: { alert__warning: 'cl-test-class-one' } }; const b = { elements: { alert__warning: 'cl-test-class-two' } }; expect(mergeAppearance(a, b)).toStrictEqual({ - layout: {}, + options: {}, elements: { alert__warning: 'cl-test-class-one cl-test-class-two', }, @@ -334,7 +337,7 @@ if (import.meta.vitest) { const a = { elements: { alert__warning: { background: 'tomato' } } }; const b = { elements: { alert__warning: { color: 'red' } } }; expect(mergeAppearance(a, b)).toStrictEqual({ - layout: {}, + options: {}, elements: { alert__warning: { color: 'red', background: 'tomato' }, }, @@ -345,7 +348,7 @@ if (import.meta.vitest) { const a = { elements: { alert__warning: { background: 'tomato' } } }; const b = { elements: { alert__warning: { background: 'red' } } }; expect(mergeAppearance(a, b)).toStrictEqual({ - layout: {}, + options: {}, elements: { alert__warning: { background: 'red' }, }, @@ -420,7 +423,7 @@ if (import.meta.vitest) { }; expect(applyTheme(theme, appearance)).toStrictEqual({ theme, - layout: defaultAppearance.layout, + options: defaultAppearance.options, elements: { ...fullTheme, alert__warning: { diff --git a/packages/ui/src/hooks/use-attributes.ts b/packages/ui/src/hooks/use-attributes.ts index 07e517998aa..8296815db8b 100644 --- a/packages/ui/src/hooks/use-attributes.ts +++ b/packages/ui/src/hooks/use-attributes.ts @@ -1,11 +1,34 @@ import type { Attribute, AttributeData } from '@clerk/types'; +import { useAppearance } from '~/contexts'; + import { useEnvironment } from './use-environment'; export function useAttributes(attribute: Attribute): AttributeData { const environment = useEnvironment(); - const userSettingsAttributes = environment.userSettings.attributes; return userSettingsAttributes[attribute]; } + +type SignUpAttributeData = AttributeData & { + /** + * Should the field be visible to the user durning sign up flow. + */ + show: boolean; +}; + +/** + * Custom attributes for sign up flow that includes whether or not a field should be shown + * based on enabled/required and showOptionalFields layout prop. + */ +export function useSignUpAttributes(attribute: Attribute): SignUpAttributeData { + const attr = useAttributes(attribute); + const { options } = useAppearance().parsedAppearance; + const { showOptionalFields } = options; + + return { + ...attr, + show: (showOptionalFields || attr.required) && attr.enabled, + }; +} diff --git a/packages/ui/src/hooks/use-card.ts b/packages/ui/src/hooks/use-card.ts index 4db9adc6f20..11132788394 100644 --- a/packages/ui/src/hooks/use-card.ts +++ b/packages/ui/src/hooks/use-card.ts @@ -24,20 +24,23 @@ import { useDisplayConfig } from './use-display-config'; */ export function useCard() { - const { layout } = useAppearance().parsedAppearance; + const { options } = useAppearance().parsedAppearance; const { applicationName, branded, logoImageUrl, homeUrl } = useDisplayConfig(); - const logoProps = { - href: layout?.logoLinkUrl || homeUrl, - src: layout?.logoImageUrl || logoImageUrl, - alt: applicationName, - }; + const logoProps = + options?.logoVisibility === 'visible' + ? { + href: options?.logoLinkUrl || homeUrl, + src: options?.logoImageUrl || logoImageUrl, + alt: applicationName, + } + : null; const footerProps = { branded, - helpPageUrl: layout?.helpPageUrl, - privacyPageUrl: layout?.privacyPageUrl, - termsPageUrl: layout?.termsPageUrl, + helpPageUrl: options?.helpPageUrl, + privacyPageUrl: options?.privacyPageUrl, + termsPageUrl: options?.termsPageUrl, }; return { diff --git a/packages/ui/src/hooks/use-dev-mode-warning.ts b/packages/ui/src/hooks/use-dev-mode-warning.ts index 202bd4c0d9f..ef433f05059 100644 --- a/packages/ui/src/hooks/use-dev-mode-warning.ts +++ b/packages/ui/src/hooks/use-dev-mode-warning.ts @@ -7,8 +7,8 @@ import { useEnvironment } from './use-environment'; export function useDevModeWarning() { const { displayConfig, isDevelopmentOrStaging } = useEnvironment(); const isDevelopment = isDevelopmentOrStaging(); - const { layout } = useAppearance().parsedAppearance; - const unsafeDisabled = layout?.unsafe_disableDevelopmentModeWarnings || false; + const { options } = useAppearance().parsedAppearance; + const unsafeDisabled = options?.unsafe_disableDevelopmentModeWarnings || false; const developmentUiDisabled = isDevelopment && unsafeDisabled; const showDevModeWarning = useMemo( () => !developmentUiDisabled && displayConfig.showDevModeWarning, diff --git a/packages/ui/src/hooks/use-focus-input.ts b/packages/ui/src/hooks/use-focus-input.ts new file mode 100644 index 00000000000..fcd892673ce --- /dev/null +++ b/packages/ui/src/hooks/use-focus-input.ts @@ -0,0 +1,32 @@ +import * as React from 'react'; + +/** + * Programmatically set focus to an input element. + * + * @example + * import { useFocusInput } from '~/hooks/use-focus-input'; + * + * function Example() { + * const [ref, setFocus] = useInputFocus() + * return ( + * <> + * + * + * + * ) + * } + */ + +export function useFocusInput(): [React.RefObject, () => void] { + const ref = React.useRef(null); + const setFocus = React.useCallback(() => { + // Using requestAnimationFrame to ensure the focus is set after the browser has painted, + // which helps avoid potential issues with focus not being applied correctly. + requestAnimationFrame(() => { + if (ref.current) { + ref.current.focus(); + } + }); + }, [ref]); + return [ref, setFocus]; +} diff --git a/packages/ui/src/primitives/animated.tsx b/packages/ui/src/primitives/animated.tsx index b2b3a5c1a32..0877fc5513c 100644 --- a/packages/ui/src/primitives/animated.tsx +++ b/packages/ui/src/primitives/animated.tsx @@ -9,7 +9,7 @@ export const Animated = (props: AnimatedProps) => { const { children, asChild } = props; // TODO: Once https://github.com/clerk/javascript/pull/3976 has been merged read from parsedLayout // const { animations } = useAppearance().parsedLayout; - const { animations } = useAppearance().parsedAppearance.layout; + const { animations } = useAppearance().parsedAppearance.options; const [parent] = useAutoAnimate(); const ref = animations !== false ? parent : null; diff --git a/packages/ui/theme-builder/app/theme-builder.tsx b/packages/ui/theme-builder/app/theme-builder.tsx index f13330cf8ac..8464144acab 100644 --- a/packages/ui/theme-builder/app/theme-builder.tsx +++ b/packages/ui/theme-builder/app/theme-builder.tsx @@ -266,7 +266,7 @@ export function ThemeBuilder({ children }: { children: React.ReactNode }) {