diff --git a/package-lock.json b/package-lock.json index 5dbddd1018a..2bee1a0c3ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38043,10 +38043,10 @@ }, "packages/backend-core": { "name": "@clerk/backend-core", - "version": "2.9.2", + "version": "2.9.3-staging.1", "license": "MIT", "dependencies": { - "@clerk/types": "^3.13.0", + "@clerk/types": "^3.14.0-staging.1", "@peculiar/webcrypto": "^1.4.0", "@types/node": "^16.11.55", "@types/node-fetch": "^2", @@ -38093,11 +38093,11 @@ }, "packages/clerk-js": { "name": "@clerk/clerk-js", - "version": "4.12.0", + "version": "4.13.0-staging.1", "license": "MIT", "dependencies": { - "@clerk/shared": "^0.0.6", - "@clerk/types": "^3.13.0", + "@clerk/shared": "^0.4.2-staging.1", + "@clerk/types": "^3.14.0-staging.1", "@emotion/cache": "^11.7.1", "@emotion/react": "^11.9.0", "@floating-ui/react-dom-interactions": "^0.6.3", @@ -38256,11 +38256,11 @@ }, "packages/edge": { "name": "@clerk/edge", - "version": "1.12.5", + "version": "1.12.6-staging.1", "license": "MIT", "dependencies": { - "@clerk/backend-core": "^2.9.2", - "@clerk/types": "^3.13.0", + "@clerk/backend-core": "^2.9.3-staging.1", + "@clerk/types": "^3.14.0-staging.1", "@peculiar/webcrypto": "^1.4.0", "@types/node": "^16.11.55", "next": "^12.2.0" @@ -38622,16 +38622,16 @@ }, "packages/expo": { "name": "@clerk/clerk-expo", - "version": "0.9.66", + "version": "0.9.68-staging.1", "license": "MIT", "dependencies": { - "@clerk/clerk-js": "^4.12.0", - "@clerk/clerk-react": "^4.3.2", + "@clerk/clerk-js": "^4.13.0-staging.1", + "@clerk/clerk-react": "^4.3.3-staging.1", "base-64": "^1.0.0", "react-native-url-polyfill": "^1.3.0" }, "devDependencies": { - "@clerk/types": "^3.13.0", + "@clerk/types": "^3.14.0-staging.1", "@types/jest": "^27.4.0", "@types/node": "^16.11.55", "@types/react": "^17.0.39", @@ -38695,6 +38695,22 @@ "node": ">=14" } }, + "packages/gatsby-plugin-clerk/node_modules/@clerk/clerk-react": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-4.3.2.tgz", + "integrity": "sha512-jsi3RF2a83mTIEaCV3mWKDSdJejmOYFq7TicY6tDYAZ6iEhzujDqZj+oR6pGWqYyyvh9CSfLMz+UhKy4MRtujA==", + "dependencies": { + "@clerk/types": "^3.13.0", + "swr": "^1.3.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16" + } + }, "packages/gatsby-plugin-clerk/node_modules/@clerk/clerk-sdk-node": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@clerk/clerk-sdk-node/-/clerk-sdk-node-4.3.0.tgz", @@ -38732,6 +38748,17 @@ "node": ">=8" } }, + "packages/gatsby-plugin-clerk/node_modules/@clerk/types": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-3.13.0.tgz", + "integrity": "sha512-BI8Cxj7U01PppOQw6ohK0US2hYM81HjQeAwt0g0RX7ne9M71XuP1Ul2MbcrtNXITmGgDCSPhb3uiw+oA+9wOBA==", + "dependencies": { + "csstype": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, "packages/gatsby-plugin-clerk/node_modules/@types/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.0.tgz", @@ -38753,13 +38780,13 @@ }, "packages/nextjs": { "name": "@clerk/nextjs", - "version": "4.5.5", + "version": "4.5.6-staging.1", "license": "MIT", "dependencies": { - "@clerk/clerk-react": "^4.3.2", - "@clerk/clerk-sdk-node": "^4.4.5", - "@clerk/edge": "^1.12.5", - "@clerk/types": "^3.13.0", + "@clerk/clerk-react": "^4.3.3-staging.1", + "@clerk/clerk-sdk-node": "^4.4.6-staging.1", + "@clerk/edge": "^1.12.6-staging.1", + "@clerk/types": "^3.14.0-staging.1", "tslib": "^2.3.1" }, "devDependencies": { @@ -39085,10 +39112,11 @@ }, "packages/react": { "name": "@clerk/clerk-react", - "version": "4.3.2", + "version": "4.3.3-staging.1", "license": "MIT", "dependencies": { - "@clerk/types": "^3.13.0", + "@clerk/shared": "^0.4.2-staging.1", + "@clerk/types": "^3.14.0-staging.1", "swr": "^1.3.0", "tslib": "^2.3.1" }, @@ -39126,12 +39154,12 @@ }, "packages/remix": { "name": "@clerk/remix", - "version": "1.3.9", + "version": "1.3.10-staging.1", "license": "MIT", "dependencies": { - "@clerk/clerk-react": "^4.3.2", - "@clerk/clerk-sdk-node": "^4.4.5", - "@clerk/types": "^3.13.0", + "@clerk/clerk-react": "^4.3.3-staging.1", + "@clerk/clerk-sdk-node": "^4.4.6-staging.1", + "@clerk/types": "^3.14.0-staging.1", "cookie": "^0.5.0", "tslib": "^2.3.1" }, @@ -39177,11 +39205,11 @@ }, "packages/sdk-node": { "name": "@clerk/clerk-sdk-node", - "version": "4.4.5", + "version": "4.4.6-staging.1", "license": "MIT", "dependencies": { - "@clerk/backend-core": "^2.9.2", - "@clerk/types": "^3.13.0", + "@clerk/backend-core": "^2.9.3-staging.1", + "@clerk/types": "^3.14.0-staging.1", "@peculiar/webcrypto": "^1.4.0", "@types/cookies": "^0.7.7", "@types/express": "^4.17.11", @@ -39223,12 +39251,13 @@ }, "packages/shared": { "name": "@clerk/shared", - "version": "0.0.6", + "version": "0.4.2-staging.1", "license": "ISC", "devDependencies": { - "@clerk/types": "^3.13.0", + "@clerk/types": "^3.14.0-staging.1", "@types/js-cookie": "^3.0.2", "js-cookie": "^3.0.1", + "swr": "^1.3.0", "tsup": "^6.2.3", "typescript": "*", "vitest": "^0.23.4" @@ -39245,10 +39274,10 @@ }, "packages/themes": { "name": "@clerk/themes", - "version": "1.2.20", + "version": "1.2.21-staging.1", "license": "MIT", "devDependencies": { - "@clerk/types": "^3.13.0", + "@clerk/types": "^3.14.0-staging.1", "typescript": "*" }, "engines": { @@ -39260,7 +39289,7 @@ }, "packages/types": { "name": "@clerk/types", - "version": "3.13.0", + "version": "3.14.0-staging.1", "license": "MIT", "dependencies": { "csstype": "^3.1.0" @@ -40732,7 +40761,7 @@ "@clerk/backend-core": { "version": "file:packages/backend-core", "requires": { - "@clerk/types": "^3.13.0", + "@clerk/types": "^3.14.0-staging.1", "@peculiar/webcrypto": "^1.4.0", "@types/jest": "^27.4.0", "@types/node": "^16.11.55", @@ -40757,9 +40786,9 @@ "@clerk/clerk-expo": { "version": "file:packages/expo", "requires": { - "@clerk/clerk-js": "^4.12.0", - "@clerk/clerk-react": "^4.3.2", - "@clerk/types": "^3.13.0", + "@clerk/clerk-js": "^4.13.0-staging.1", + "@clerk/clerk-react": "^4.3.3-staging.1", + "@clerk/types": "^3.14.0-staging.1", "@types/jest": "^27.4.0", "@types/node": "^16.11.55", "@types/react": "^17.0.39", @@ -40791,8 +40820,8 @@ "@babel/preset-env": "^7.12.1", "@babel/preset-react": "^7.12.5", "@babel/preset-typescript": "^7.12.1", - "@clerk/shared": "^0.0.6", - "@clerk/types": "^3.13.0", + "@clerk/shared": "^0.4.2-staging.1", + "@clerk/types": "^3.14.0-staging.1", "@emotion/cache": "^11.7.1", "@emotion/react": "^11.9.0", "@floating-ui/react-dom-interactions": "^0.6.3", @@ -40900,7 +40929,8 @@ "@clerk/clerk-react": { "version": "file:packages/react", "requires": { - "@clerk/types": "^3.13.0", + "@clerk/shared": "^0.4.2-staging.1", + "@clerk/types": "^3.14.0-staging.1", "@testing-library/dom": "^7.28.1", "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^11.2.1", @@ -40933,8 +40963,8 @@ "@clerk/clerk-sdk-node": { "version": "file:packages/sdk-node", "requires": { - "@clerk/backend-core": "^2.9.2", - "@clerk/types": "^3.13.0", + "@clerk/backend-core": "^2.9.3-staging.1", + "@clerk/types": "^3.14.0-staging.1", "@peculiar/webcrypto": "^1.4.0", "@types/cookies": "^0.7.7", "@types/express": "^4.17.11", @@ -40971,8 +41001,8 @@ "@clerk/edge": { "version": "file:packages/edge", "requires": { - "@clerk/backend-core": "^2.9.2", - "@clerk/types": "^3.13.0", + "@clerk/backend-core": "^2.9.3-staging.1", + "@clerk/types": "^3.14.0-staging.1", "@peculiar/webcrypto": "^1.4.0", "@types/jest": "^27.4.0", "@types/node": "^16.11.55", @@ -41113,10 +41143,10 @@ "@clerk/nextjs": { "version": "file:packages/nextjs", "requires": { - "@clerk/clerk-react": "^4.3.2", - "@clerk/clerk-sdk-node": "^4.4.5", - "@clerk/edge": "^1.12.5", - "@clerk/types": "^3.13.0", + "@clerk/clerk-react": "^4.3.3-staging.1", + "@clerk/clerk-sdk-node": "^4.4.6-staging.1", + "@clerk/edge": "^1.12.6-staging.1", + "@clerk/types": "^3.14.0-staging.1", "@types/jest": "^27.4.0", "@types/node": "^16.11.55", "@types/react": "^17.0.39", @@ -41281,9 +41311,9 @@ "@clerk/remix": { "version": "file:packages/remix", "requires": { - "@clerk/clerk-react": "^4.3.2", - "@clerk/clerk-sdk-node": "^4.4.5", - "@clerk/types": "^3.13.0", + "@clerk/clerk-react": "^4.3.3-staging.1", + "@clerk/clerk-sdk-node": "^4.4.6-staging.1", + "@clerk/types": "^3.14.0-staging.1", "@types/cookie": "^0.5.0", "@types/jest": "^27.4.0", "@types/node": "^16.11.55", @@ -41320,9 +41350,10 @@ "@clerk/shared": { "version": "file:packages/shared", "requires": { - "@clerk/types": "^3.13.0", + "@clerk/types": "^3.14.0-staging.1", "@types/js-cookie": "^3.0.2", "js-cookie": "^3.0.1", + "swr": "^1.3.0", "tsup": "^6.2.3", "typescript": "*", "vitest": "^0.23.4" @@ -41331,7 +41362,7 @@ "@clerk/themes": { "version": "file:packages/themes", "requires": { - "@clerk/types": "^3.13.0", + "@clerk/types": "^3.14.0-staging.1", "typescript": "*" } }, @@ -55133,6 +55164,16 @@ "tslib": "^2.3.1" } }, + "@clerk/clerk-react": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@clerk/clerk-react/-/clerk-react-4.3.2.tgz", + "integrity": "sha512-jsi3RF2a83mTIEaCV3mWKDSdJejmOYFq7TicY6tDYAZ6iEhzujDqZj+oR6pGWqYyyvh9CSfLMz+UhKy4MRtujA==", + "requires": { + "@clerk/types": "^3.13.0", + "swr": "^1.3.0", + "tslib": "^2.3.1" + } + }, "@clerk/clerk-sdk-node": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@clerk/clerk-sdk-node/-/clerk-sdk-node-4.3.0.tgz", @@ -55166,6 +55207,14 @@ } } }, + "@clerk/types": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@clerk/types/-/types-3.13.0.tgz", + "integrity": "sha512-BI8Cxj7U01PppOQw6ohK0US2hYM81HjQeAwt0g0RX7ne9M71XuP1Ul2MbcrtNXITmGgDCSPhb3uiw+oA+9wOBA==", + "requires": { + "csstype": "^3.1.0" + } + }, "@types/cookie": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.5.0.tgz", diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 1bdb98e41bb..cca27e4db80 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/backend-core", - "version": "2.9.2", + "version": "2.9.3-staging.1", "license": "MIT", "description": "Clerk Backend API core resources and authentication utilities for JavaScript environments.", "scripts": { @@ -14,7 +14,7 @@ "main": "dist/cjs/index.js", "module": "dist/mjs/index.js", "dependencies": { - "@clerk/types": "^3.13.0", + "@clerk/types": "^3.14.0-staging.1", "@peculiar/webcrypto": "^1.4.0", "@types/node": "^16.11.55", "@types/node-fetch": "^2", diff --git a/packages/clerk-js/CHANGELOG.md b/packages/clerk-js/CHANGELOG.md index 7f444192edd..2a61ebfebb5 100644 --- a/packages/clerk-js/CHANGELOG.md +++ b/packages/clerk-js/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +### [4.12.1](https://github.com/clerkinc/javascript/compare/@clerk/clerk-js@4.12.0...@clerk/clerk-js@4.12.1) (2022-10-24) + +### Bug Fixes + +- **clerk-js:** Add missing localizationKey import ([fb17eca](https://github.com/clerkinc/javascript/commit/fb17ecac5a054027078ce1a7d8700cb497c526fa)) + ## [4.12.0](https://github.com/clerkinc/javascript/compare/@clerk/clerk-js@4.12.0-staging.1...@clerk/clerk-js@4.12.0) (2022-10-24) **Note:** Version bump only for package @clerk/clerk-js diff --git a/packages/clerk-js/package.json b/packages/clerk-js/package.json index f75993fbcec..7f057dd3127 100644 --- a/packages/clerk-js/package.json +++ b/packages/clerk-js/package.json @@ -1,6 +1,6 @@ { "name": "@clerk/clerk-js", - "version": "4.12.0", + "version": "4.13.0-staging.1", "license": "MIT", "description": "Clerk.dev JS library", "keywords": [ @@ -38,8 +38,8 @@ "test:coverage": "jest --collectCoverage" }, "dependencies": { - "@clerk/shared": "^0.0.6", - "@clerk/types": "^3.13.0", + "@clerk/shared": "^0.4.2-staging.1", + "@clerk/types": "^3.14.0-staging.1", "@emotion/cache": "^11.7.1", "@emotion/react": "^11.9.0", "@floating-ui/react-dom-interactions": "^0.6.3", @@ -118,7 +118,7 @@ "files": [ { "path": "./dist/clerk.browser.js", - "maxSize": "165kB" + "maxSize": "169kB" }, { "path": "./dist/clerk.headless.js", diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 40d6f820abe..a846092f7cd 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -108,13 +108,14 @@ export default class Clerk implements ClerkInterface { #lastOrganizationInvitation: OrganizationInvitationResource | null = null; #lastOrganizationMember: OrganizationMembershipResource | null = null; - /** - * @inheritDoc {ClerkInterface.version} - */ get version(): string { return Clerk.version; } + get loaded(): boolean { + return this.#isReady; + } + public constructor(frontendApi: string) { if (!frontendApi) { clerkErrorNoFrontendApi(); diff --git a/packages/clerk-js/src/core/fapiClient.ts b/packages/clerk-js/src/core/fapiClient.ts index 4fb1ee4e73c..76ab9454d19 100644 --- a/packages/clerk-js/src/core/fapiClient.ts +++ b/packages/clerk-js/src/core/fapiClient.ts @@ -157,7 +157,7 @@ export default function createFapiClient(clerkInstance: Clerk): FapiClient { // In case FormData is provided, we don't want to mess with the headers, // because for file uploads the header is properly set by the browser. if (method !== 'GET' && !(body instanceof FormData)) { - requestInit.body = qs.stringify(body, { encoder: camelToSnakeEncoder }); + requestInit.body = qs.stringify(body, { encoder: camelToSnakeEncoder, indices: false }); // @ts-ignore requestInit.headers.set('Content-Type', 'application/x-www-form-urlencoded'); } diff --git a/packages/clerk-js/src/core/resources/Error.ts b/packages/clerk-js/src/core/resources/Error.ts index f6ae4ab8dd2..3b248041141 100644 --- a/packages/clerk-js/src/core/resources/Error.ts +++ b/packages/clerk-js/src/core/resources/Error.ts @@ -37,6 +37,7 @@ export function parseError(error: ClerkAPIErrorJSON): ClerkAPIError { meta: { paramName: error?.meta?.param_name, sessionId: error?.meta?.session_id, + emailAddresses: error?.meta?.email_addresses, }, }; } diff --git a/packages/clerk-js/src/core/resources/Organization.ts b/packages/clerk-js/src/core/resources/Organization.ts index 29dad478c61..e180bf8921a 100644 --- a/packages/clerk-js/src/core/resources/Organization.ts +++ b/packages/clerk-js/src/core/resources/Organization.ts @@ -1,13 +1,16 @@ import type { + AddMemberParams, CreateOrganizationParams, GetMembershipsParams, GetPendingInvitationsParams, - MembershipRole, + InviteMemberParams, + InviteMembersParams, OrganizationInvitationJSON, OrganizationJSON, OrganizationMembershipJSON, OrganizationResource, SetOrganizationLogoParams, + UpdateMembershipParams, UpdateOrganizationParams, } from '@clerk/types'; @@ -100,8 +103,12 @@ export class Organization extends BaseResource implements OrganizationResource { return newMember; }; - inviteMember = async (inviteMemberParams: InviteMemberParams) => { - return await OrganizationInvitation.create(this.id, inviteMemberParams); + inviteMember = async (params: InviteMemberParams) => { + return OrganizationInvitation.create(this.id, params); + }; + + inviteMembers = async (params: InviteMembersParams) => { + return OrganizationInvitation.createBulk(this.id, params); }; updateMember = async ({ userId, role }: UpdateMembershipParams): Promise => { @@ -149,24 +156,3 @@ export class Organization extends BaseResource implements OrganizationResource { return this; } } - -export type GetOrganizationParams = { - limit?: number; - offset?: number; -}; - -export type AddMemberParams = { - userId: string; - role: MembershipRole; -}; - -export type InviteMemberParams = { - emailAddress: string; - role: MembershipRole; - redirectUrl?: string; -}; - -export type UpdateMembershipParams = { - userId: string; - role: MembershipRole; -}; diff --git a/packages/clerk-js/src/core/resources/OrganizationInvitation.ts b/packages/clerk-js/src/core/resources/OrganizationInvitation.ts index cdeace51c06..240d0db7ed9 100644 --- a/packages/clerk-js/src/core/resources/OrganizationInvitation.ts +++ b/packages/clerk-js/src/core/resources/OrganizationInvitation.ts @@ -1,4 +1,6 @@ import { + CreateBulkOrganizationInvitationParams, + CreateOrganizationInvitationParams, MembershipRole, OrganizationInvitationJSON, OrganizationInvitationResource, @@ -25,19 +27,32 @@ export class OrganizationInvitation extends BaseResource implements Organization await BaseResource._fetch({ path: `/organizations/${organizationId}/invitations`, method: 'POST', - body: { - email_address: emailAddress, - role, - redirect_url: redirectUrl, - } as any, + body: { email_address: emailAddress, role, redirect_url: redirectUrl } as any, }) )?.response as unknown as OrganizationInvitationJSON; - const newInvitation = new OrganizationInvitation(json); this.clerk.__unstable__invitationUpdate(newInvitation); return newInvitation; } + static async createBulk( + organizationId: string, + params: CreateBulkOrganizationInvitationParams, + ): Promise { + const { emailAddresses, redirectUrl, role } = params; + const json = ( + await BaseResource._fetch({ + path: `/organizations/${organizationId}/invitations/bulk`, + method: 'POST', + body: { email_address: emailAddresses, role, redirect_url: redirectUrl } as any, + }) + )?.response as unknown as OrganizationInvitationJSON[]; + // const newInvitation = new OrganizationInvitation(json); + // TODO: Figure out what this is... + // this.clerk.__unstable__invitationUpdate(newInvitation); + return json.map(invitationJson => new OrganizationInvitation(invitationJson)); + } + constructor(data: OrganizationInvitationJSON) { super(); this.fromJSON(data); @@ -62,9 +77,3 @@ export class OrganizationInvitation extends BaseResource implements Organization return this; } } - -export type CreateOrganizationInvitationParams = { - emailAddress: string; - role: MembershipRole; - redirectUrl?: string; -}; diff --git a/packages/clerk-js/src/core/resources/__snapshots__/Organization.test.ts.snap b/packages/clerk-js/src/core/resources/__snapshots__/Organization.test.ts.snap index 0789cdc9f90..12a07a1d5e6 100644 --- a/packages/clerk-js/src/core/resources/__snapshots__/Organization.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__snapshots__/Organization.test.ts.snap @@ -9,6 +9,7 @@ Organization { "getPendingInvitations": [Function], "id": "test_id", "inviteMember": [Function], + "inviteMembers": [Function], "logoUrl": "https://url-for-logo.png", "name": "test_name", "pathRoot": "/organizations", diff --git a/packages/clerk-js/src/core/resources/__snapshots__/OrganizationMembership.test.ts.snap b/packages/clerk-js/src/core/resources/__snapshots__/OrganizationMembership.test.ts.snap index 9464eaf2ab3..d042cae6e6e 100644 --- a/packages/clerk-js/src/core/resources/__snapshots__/OrganizationMembership.test.ts.snap +++ b/packages/clerk-js/src/core/resources/__snapshots__/OrganizationMembership.test.ts.snap @@ -13,6 +13,7 @@ OrganizationMembership { "getPendingInvitations": [Function], "id": "test_org_id", "inviteMember": [Function], + "inviteMembers": [Function], "logoUrl": "https://path-to-logo.png", "name": "test_name", "pathRoot": "/organizations", diff --git a/packages/clerk-js/src/ui/Components.tsx b/packages/clerk-js/src/ui/Components.tsx index 6f1cdafcba5..42e9b2d95ae 100644 --- a/packages/clerk-js/src/ui/Components.tsx +++ b/packages/clerk-js/src/ui/Components.tsx @@ -6,7 +6,8 @@ import ReactDOM from 'react-dom'; import { PRESERVED_QUERYSTRING_PARAMS } from '../core/constants'; import { clerkUIErrorDOMElementNotFound } from '../core/errors'; -import { OrganizationProfile } from './components/OrganizationProfile'; +import { ImpersonationFab } from './components/ImpersonationFab'; +import { OrganizationProfile, OrganizationProfileModal } from './components/OrganizationProfile'; import { OrganizationSwitcher } from './components/OrganizationSwitcher'; import { SignIn, SignInModal } from './components/SignIn'; import { SignUp, SignUpModal } from './components/SignUp'; @@ -17,7 +18,6 @@ import { CoreClerkContextWrapper } from './contexts/CoreClerkContextWrapper'; import { AppearanceProvider } from './customizables'; import { FlowMetadataProvider, Modal } from './elements'; import { useSafeLayoutEffect } from './hooks'; -import { ImpersonationFab } from './ImpersonationFab'; import Portal from './portal'; import { VirtualRouter } from './router'; import { InternalThemeProvider } from './styledSystem'; @@ -239,15 +239,8 @@ const Components = (props: ComponentsProps) => { componentsControls.closeModal('userProfile')} - containerSx={{ - alignItems: 'center', - }} - contentSx={t => ({ - height: `min(${t.sizes.$176}, calc(100% - ${t.sizes.$12}))`, - margin: 0, - // height: t.sizes.$176, - // maxHeight: `min(${t.sizes.$176}, calc(100vh - ${t.sizes.$20}))`, - })} + containerSx={{ alignItems: 'center' }} + contentSx={t => ({ height: `min(${t.sizes.$176}, calc(100% - ${t.sizes.$12}))`, margin: 0 })} > { componentsControls.closeModal('organizationProfile')} containerSx={{ alignItems: 'center' }} - contentSx={t => ({ - height: `min(${t.sizes.$176}, calc(100% - ${t.sizes.$12}))`, - margin: 0, - })} + contentSx={t => ({ height: `min(${t.sizes.$176}, calc(100% - ${t.sizes.$12}))`, margin: 0 })} > componentsControls.closeModal('organizationProfile')} - startPath='/user' + startPath='/organizationProfile' > - hello from org profil + @@ -325,6 +315,7 @@ const Components = (props: ComponentsProps) => { ); })} + {signInModal && mountedSignInModal} {signUpModal && mountedSignUpModal} {userProfileModal && mountedUserProfileModal} diff --git a/packages/clerk-js/src/ui/common/withRedirectToHome.test.tsx b/packages/clerk-js/src/ui/common/withRedirectToHome.test.tsx index 82a136dbfb5..97d606822ba 100644 --- a/packages/clerk-js/src/ui/common/withRedirectToHome.test.tsx +++ b/packages/clerk-js/src/ui/common/withRedirectToHome.test.tsx @@ -3,7 +3,7 @@ import { EnvironmentResource } from '@clerk/types'; import React from 'react'; import { AuthConfig, DisplayConfig } from '../../core/resources'; -import { useCoreSession, useEnvironment } from '../../ui/contexts'; +import { CoreSessionContext, useEnvironment } from '../../ui/contexts'; import { withRedirectToHome } from './withRedirectToHome'; const mockNavigate = jest.fn(); @@ -12,7 +12,7 @@ jest.mock('ui/hooks', () => ({ })); jest.mock('ui/contexts', () => ({ - useCoreSession: jest.fn(), + ...jest.requireActual('ui/contexts'), useEnvironment: jest.fn(), })); @@ -26,10 +26,6 @@ describe('withRedirectToHome(Component)', () => { describe('when there is a session', () => { describe('and the instance is in single session mode', () => { beforeEach(() => { - (useCoreSession as jest.Mock).mockImplementation(() => ({ - id: 'sess_id', - })); - (useEnvironment as jest.Mock).mockImplementation( () => ({ @@ -45,7 +41,11 @@ describe('withRedirectToHome(Component)', () => { it('navigates to home_url', () => { const Component = withRedirectToHome(Tester); - render(); + render( + + + , + ); expect(mockNavigate).toHaveBeenNthCalledWith(1, 'http://my-home.com'); expect(screen.queryByText('Tester')).not.toBeInTheDocument(); }); @@ -53,10 +53,6 @@ describe('withRedirectToHome(Component)', () => { describe('and the instance is not in single session mode', () => { beforeEach(() => { - (useCoreSession as jest.Mock).mockImplementation(() => ({ - id: 'sess_id', - })); - (useEnvironment as jest.Mock).mockImplementation( () => ({ @@ -72,7 +68,11 @@ describe('withRedirectToHome(Component)', () => { it('renders the wrapped component', () => { const Component = withRedirectToHome(Tester); - render(); + render( + + + , + ); expect(mockNavigate).not.toHaveBeenCalled(); expect(screen.queryByText('Tester')).toBeInTheDocument(); }); @@ -81,8 +81,6 @@ describe('withRedirectToHome(Component)', () => { describe('when there is no session', () => { beforeEach(() => { - (useCoreSession as jest.Mock).mockImplementation(() => undefined); - (useEnvironment as jest.Mock).mockImplementation( () => ({ @@ -98,7 +96,11 @@ describe('withRedirectToHome(Component)', () => { it('renders the wrapped component', () => { const Component = withRedirectToHome(Tester); - render(); + render( + + + , + ); expect(mockNavigate).not.toHaveBeenCalled(); expect(screen.queryByText('Tester')).toBeInTheDocument(); }); diff --git a/packages/clerk-js/src/ui/common/withRedirectToHome.tsx b/packages/clerk-js/src/ui/common/withRedirectToHome.tsx index 2fd71131750..f709bb9e511 100644 --- a/packages/clerk-js/src/ui/common/withRedirectToHome.tsx +++ b/packages/clerk-js/src/ui/common/withRedirectToHome.tsx @@ -1,7 +1,8 @@ import { SignInProps, SignUpProps } from '@clerk/types'; -import React from 'react'; +import React, { useContext } from 'react'; -import { useCoreSession, useEnvironment } from '../contexts'; +import { useEnvironment } from '../contexts'; +import { CoreSessionContext } from '../contexts/CoreSessionContext'; import { useNavigate } from '../hooks'; export function withRedirectToHome

( @@ -15,7 +16,8 @@ export function withRedirectToHome

{ if (singleSessionMode && !!session) { diff --git a/packages/clerk-js/src/ui/ImpersonationFab/ImpersonationFab.tsx b/packages/clerk-js/src/ui/components/ImpersonationFab/ImpersonationFab.tsx similarity index 93% rename from packages/clerk-js/src/ui/ImpersonationFab/ImpersonationFab.tsx rename to packages/clerk-js/src/ui/components/ImpersonationFab/ImpersonationFab.tsx index 6b5c7c03654..8b15f849cef 100644 --- a/packages/clerk-js/src/ui/ImpersonationFab/ImpersonationFab.tsx +++ b/packages/clerk-js/src/ui/components/ImpersonationFab/ImpersonationFab.tsx @@ -1,8 +1,6 @@ import React, { PointerEventHandler, useEffect, useRef } from 'react'; -import { mqu, PropsOfComponent } from '../../ui/styledSystem'; -import { getFullName, getIdentifier } from '../../ui/utils'; -import { useCoreClerk, useCoreSession } from '../contexts'; +import { useCoreClerk, useCoreSession, withCoreUserGuard } from '../../contexts'; import { Col, descriptors, @@ -14,9 +12,11 @@ import { Text, useAppearance, useLocalizations, -} from '../customizables'; -import { Portal } from '../elements/Portal'; -import { Eye } from '../icons'; +} from '../../customizables'; +import { Portal } from '../../elements/Portal'; +import { Eye } from '../../icons'; +import { mqu, PropsOfComponent } from '../../styledSystem'; +import { getFullName, getIdentifier } from '../../utils'; type EyeCircleProps = PropsOfComponent & { width: string; @@ -90,7 +90,7 @@ const FabContent = ({ title, signOutText }: FabContentProps) => { ); }; -export const ImpersonationFab = () => { +const _ImpersonationFab = () => { const session = useCoreSession(); const { t } = useLocalizations(); const { parsedInternalTheme } = useAppearance(); @@ -227,3 +227,5 @@ export const ImpersonationFab = () => { ); }; + +export const ImpersonationFab = withCoreUserGuard(_ImpersonationFab); diff --git a/packages/clerk-js/src/ui/ImpersonationFab/index.ts b/packages/clerk-js/src/ui/components/ImpersonationFab/index.ts similarity index 100% rename from packages/clerk-js/src/ui/ImpersonationFab/index.ts rename to packages/clerk-js/src/ui/components/ImpersonationFab/index.ts diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/ActiveMembersList.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/ActiveMembersList.tsx new file mode 100644 index 00000000000..5b02e86adb7 --- /dev/null +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/ActiveMembersList.tsx @@ -0,0 +1,99 @@ +import { MembershipRole, OrganizationMembershipResource } from '@clerk/types'; +import React, { useState } from 'react'; + +import { useCoreOrganization, useCoreUser } from '../../contexts'; +import { Badge, localizationKeys, Td, Text } from '../../customizables'; +import { ThreeDotsMenu, useCardState, usePagination, UserPreview } from '../../elements'; +import { handleError, roleLocalizationKey } from '../../utils'; +import { MembersListTable, RoleSelect, RowContainer } from './MemberListTable'; + +const MOCK_ITEM_COUNT = 28; +const ITEMS_PER_PAGE = 10; + +export const ActiveMembersList = () => { + const { page, changePage } = usePagination({ defaultPage: 1 }); + const { membershipList } = useCoreOrganization({ + membershipList: { offset: (page - 1) * ITEMS_PER_PAGE, limit: ITEMS_PER_PAGE }, + }); + + return ( + ( + + ))} + /> + ); +}; + +const MemberRow = (props: { membership: OrganizationMembershipResource }) => { + const { membership } = props; + const card = useCardState(); + const { membership: currentUserMembership } = useCoreOrganization(); + const user = useCoreUser(); + const [role, setRole] = useState(membership.role); + + const isAdmin = currentUserMembership?.role === 'admin'; + const isCurrentUser = user.id === membership.publicUserData.userId; + + const handleRoleChange = (newRole: MembershipRole) => { + if (!isAdmin) { + return; + } + return card + .runAsync(membership.update({ role: newRole })) + .then(() => setRole(newRole)) + .catch(err => handleError(err, [], card.setError)); + }; + + const handleRemove = () => { + if (!isAdmin) { + return; + } + return card.runAsync(membership.destroy()).catch(err => handleError(err, [], card.setError)); + }; + + return ( + + + } + /> + + {membership.createdAt.toLocaleDateString()} + + {isAdmin ? ( + + ) : ( + ({ opacity: t.opacity.$inactive })} + localizationKey={roleLocalizationKey(role)} + /> + )} + + + {isAdmin && ( + + )} + + + ); +}; diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/CreateOrganizationPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/CreateOrganizationPage.tsx index 4f0623858f2..3725ff19e3c 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/CreateOrganizationPage.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/CreateOrganizationPage.tsx @@ -1,90 +1,127 @@ import React from 'react'; import { useWizard, Wizard } from '../../common'; -import { - AvatarUploader, - Form, - Select, - SelectButton, - SelectOptionList, - useCardState, - withCardStateProvider, -} from '../../elements'; +import { useCoreClerk, useCoreOrganization, useCoreOrganizations, useOrganizationProfileContext } from '../../contexts'; +import { Button, Text } from '../../customizables'; +import { Form, useCardState, withCardStateProvider } from '../../elements'; import { localizationKeys } from '../../localization'; -import { useFormControl } from '../../utils'; -import { FormButtons } from '../UserProfile/FormButtons'; -import { SuccessPage } from '../UserProfile/SuccessPage'; +import { handleError, useFormControl } from '../../utils'; +import { FormButtonContainer } from '../UserProfile/FormButtons'; +import { InviteMembersForm } from './InviteMembersForm'; import { ContentPage } from './OrganizationContentPage'; +import { OrganizationProfileAvatarUploader } from './OrganizationProfileAvatarUploader'; export const CreateOrganizationPage = withCardStateProvider(() => { // const title = localizationKeys('userProfile.profilePage.title'); const title = 'Create Organization'; const subtitle = 'Set the organization profile'; const card = useCardState(); - const [avatarChanged, setAvatarChanged] = React.useState(false); + const [file, setFile] = React.useState(); + const { createOrganization } = useCoreOrganizations(); + const { setActive, closeOrganizationProfile } = useCoreClerk(); + const { mode, navigateAfterOrganizationCreationUrl } = useOrganizationProfileContext(); + const { organization } = useCoreOrganization(); const wizard = useWizard({ onNextStep: () => card.setError(undefined) }); - const organizationName = useFormControl('name', '', { + const nameField = useFormControl('name', '', { type: 'text', - // label: localizationKeys('formFieldLabel__firstName'), - // placeholder: localizationKeys('formFieldInputPlaceholder__firstName'), - label: 'Organization Name', - placeholder: '', + label: localizationKeys('formFieldLabel__organizationName'), + placeholder: localizationKeys('formFieldInputPlaceholder__organizationName'), }); - const dataChanged = 'placeholder' !== organizationName.value; - const canSubmit = dataChanged || avatarChanged; + const dataChanged = !!nameField.value; + const canSubmit = dataChanged || !!file; const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); - wizard.nextStep(); + if (!canSubmit) { + return; + } - // return ( - // dataChanged - // ? user.update({ firstName: organizationName.value, lastName: lastNameField.value }) - // : Promise.resolve() - // ) - // .then(() => { - // wizard.nextStep(); - // }) - // .catch(err => { - // handleError(err, [organizationName, lastNameField], card.setError); - // }); + return createOrganization?.({ name: nameField.value }) + .then(org => (file ? org.setLogo({ file }) : org)) + .then(org => setActive({ organization: org })) + .then(wizard.nextStep) + .catch(err => handleError(err, [nameField], card.setError)); }; - const uploadAvatar = (file: File) => { - // return user.setProfileImage({ file }).then(() => { - // setAvatarChanged(true); - // }); + const completeFlow = () => { + navigateAfterOrganizationCreationUrl(); + if (mode === 'modal') { + closeOrganizationProfile(); + } }; return ( - + await setFile(file)} + /> - + + + {mode === 'modal' && ( + + )} + - + + + + {/**/} + + + + - - - {actions.map(action => { - return ( - action.onClick(user)} - > - {action.label} - - ); - })} - - - - + ); }; diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationContentPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationContentPage.tsx index 2f64308b090..889d4f2780c 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationContentPage.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationContentPage.tsx @@ -2,11 +2,12 @@ import { ContentPage as BaseContentPage } from '../../elements'; import { PropsOfComponent } from '../../styledSystem'; import { OrganizationProfileBreadcrumbs } from './OrganizationProfileNavbar'; -export const ContentPage = (props: Omit, 'Breadcrumbs'>) => { +export const ContentPage = (props: PropsOfComponent) => { + const { Breadcrumbs } = props; return ( ); }; diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationMembers.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationMembers.tsx index d83cc7a7f9c..0556e093a0c 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationMembers.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationMembers.tsx @@ -1,21 +1,36 @@ import React from 'react'; -import { useEnvironment } from '../../contexts'; +import { useCoreOrganization } from '../../contexts'; import { Col, descriptors, Flex, localizationKeys } from '../../customizables'; -import { Header, IconButton, NavbarMenuButtonRow, Tab, TabPanel, TabPanels, Tabs, TabsList } from '../../elements'; +import { + CardAlert, + Header, + IconButton, + NavbarMenuButtonRow, + Tab, + TabPanel, + TabPanels, + Tabs, + TabsList, + useCardState, +} from '../../elements'; import { useNavigate } from '../../hooks'; import { UserAdd } from '../../icons'; -import { MembersListTable } from './MemberListTable'; +import { ActiveMembersList } from './ActiveMembersList'; +import { InvitedMembersList } from './InvitedMembersList'; export const OrganizationMembers = () => { const { navigate } = useNavigate(); - const { attributes } = useEnvironment().userSettings; + const card = useCardState(); + const { membership } = useCoreOrganization(); + const isAdmin = membership?.role === 'admin'; return ( + {card.error} { localizationKey={'View and manage organization members'} /> - navigate('invite-members')} - icon={UserAdd} - textVariant='buttonExtraSmallBold' - > - Invite - + {isAdmin && ( + navigate('invite-members')} + icon={UserAdd} + textVariant='buttonExtraSmallBold' + > + Invite + + )} Active - Invited + {isAdmin && Invited} - - - + {isAdmin && ( + + + + )} ); }; - -const mockUsers = Array.from({ length: 34 }, (_, i) => ({ - id: i.toString(), - firstName: `Corey${i}`, - lastName: `Doken${i}`, - primaryEmailAddress: { - emailAddress: `hello${i}@gmail.com`, - }, - role: i % 2 ? 'admin' : 'basic_member', -})); - -const ActiveMembersList = () => { - return ( - {} }]} - /> - ); -}; - -const InvitedMembersList = () => { - return ( - {} }, - { label: 'Revoke invitation', destructive: true, onClick: () => {} }, - ]} - /> - ); -}; diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx index 9f9fbb219d9..86360703eb8 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx @@ -1,14 +1,26 @@ import { OrganizationProfileProps } from '@clerk/types/src'; import React from 'react'; -import { withCoreUserGuard } from '../../contexts'; +import { + ComponentContext, + useCoreOrganization, + useOrganizationProfileContext, + withCoreUserGuard, +} from '../../contexts'; import { Flow } from '../../customizables'; import { ProfileCard, withCardStateProvider } from '../../elements'; import { Route, Switch } from '../../router'; +import { OrganizationProfileCtx } from '../../types'; import { OrganizationProfileNavbar } from './OrganizationProfileNavbar'; -import { OrganizationProfileRoutes } from './OrganizationProfileRoutes'; +import { CreateOrganizationRoutes, OrganizationProfileRoutes } from './OrganizationProfileRoutes'; const _OrganizationProfile = (_: OrganizationProfileProps) => { + // TODO: Should this be a guard HOC? + const { organization } = useCoreOrganization(); + if (!organization) { + return null; + } + return ( @@ -26,6 +38,16 @@ const _OrganizationProfile = (_: OrganizationProfileProps) => { const AuthenticatedRoutes = withCoreUserGuard(() => { const contentRef = React.useRef(null); + const { new: showCreateOrganization } = useOrganizationProfileContext(); + + if (showCreateOrganization) { + return ( + + + + ); + } + return ( @@ -37,24 +59,22 @@ const AuthenticatedRoutes = withCoreUserGuard(() => { export const OrganizationProfile = withCardStateProvider(_OrganizationProfile); -// @ts-ignore export const OrganizationProfileModal = (props: OrganizationProfileProps): JSX.Element => { - // const userProfileProps: OrganizationProfileCtx = { - // ...props, - // routing: 'virtual', - // componentName: 'OrganizationProfile', - // mode: 'modal', - // }; + const organizationProfileProps: OrganizationProfileCtx = { + ...props, + routing: 'virtual', + componentName: 'OrganizationProfile', + mode: 'modal', + }; return ( - - {/**/} - {/*TODO: Used by InvisibleRootBox, can we simplify? */} -

- {/**/} - -
- {/**/} + + + {/*TODO: Used by InvisibleRootBox, can we simplify? */} +
+ +
+
); }; diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileAvatarUploader.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileAvatarUploader.tsx new file mode 100644 index 00000000000..f4f61a7cd90 --- /dev/null +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileAvatarUploader.tsx @@ -0,0 +1,23 @@ +import { OrganizationResource } from '@clerk/types'; + +import { AvatarUploader, AvatarUploaderProps, OrganizationAvatar } from '../../elements'; +import { localizationKeys } from '../../localization'; + +export const OrganizationProfileAvatarUploader = ( + props: Omit & { organization: Partial }, +) => { + const { organization, ...rest } = props; + return ( + theme.sizes.$11} + optimize + {...organization} + /> + } + /> + ); +}; diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileNavbar.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileNavbar.tsx index 40726ec0ac8..c69623f14fb 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileNavbar.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileNavbar.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import { useCoreOrganization } from '../../contexts'; import { Breadcrumbs, NavBar, NavbarContextProvider, NavbarRoute, OrganizationPreview } from '../../elements'; import { CogFilled, User } from '../../icons'; import { localizationKeys } from '../../localization'; @@ -23,17 +24,20 @@ const organizationProfileRoutes: NavbarRoute[] = [ export const OrganizationProfileNavbar = ( props: React.PropsWithChildren, 'contentRef'>>, ) => { + const { organization } = useCoreOrganization(); + + if (!organization) { + return null; + } + return ( ({ margin: `0 0 ${t.space.$2x5} ${t.space.$2}` })} + organization={organization} + sx={t => ({ margin: `0 0 ${t.space.$4} ${t.space.$2}` })} /> } routes={organizationProfileRoutes} diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx index eb9801ffc49..79089a6d0c6 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx @@ -1,7 +1,8 @@ import { ProfileCardContent } from '../../elements'; import { Route, Switch } from '../../router'; import { PropsOfComponent } from '../../styledSystem'; -import { InviteMemberPage } from './InviteMemberPage'; +import { CreateOrganizationPage } from './CreateOrganizationPage'; +import { InviteMembersPage } from './InviteMembersPage'; import { OrganizationMembers } from './OrganizationMembers'; import { OrganizationSettings } from './OrganizationSettings'; import { ProfileSettingsPage } from './ProfileSettingsPage'; @@ -22,7 +23,7 @@ export const OrganizationProfileRoutes = (props: PropsOfComponent - + @@ -32,3 +33,17 @@ export const OrganizationProfileRoutes = (props: PropsOfComponent ); }; + +export const CreateOrganizationRoutes = () => { + return ( + + + + + + + + + + ); +}; diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationSettings.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationSettings.tsx index f1fbec16e79..2515b68c095 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationSettings.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationSettings.tsx @@ -1,16 +1,14 @@ import React from 'react'; -import { useCoreUser, useEnvironment } from '../../contexts'; +import { useCoreOrganization, useCoreUser } from '../../contexts'; import { Col, descriptors, Flex, localizationKeys } from '../../customizables'; -import { Header, IconButton, NavbarMenuButtonRow, UserPreview } from '../../elements'; +import { Header, IconButton, NavbarMenuButtonRow, OrganizationPreview } from '../../elements'; import { useNavigate } from '../../hooks'; -import { Times, Trash } from '../../icons'; +import { Times } from '../../icons'; import { ProfileSection } from '../UserProfile/Section'; import { BlockButton } from '../UserProfile/UserProfileBlockButtons'; export const OrganizationSettings = () => { - const { attributes } = useEnvironment().userSettings; - return ( { }; const OrganizationProfileSection = () => { + const { organization, membership } = useCoreOrganization(); const { navigate } = useNavigate(); - const { username, primaryEmailAddress, primaryPhoneNumber, ...userWithoutIdentifiers } = useCoreUser(); + const isAdmin = membership?.role === 'admin'; + + if (!organization) { + return null; + } + + const profile = ( + + ); return ( { title={'Organization profile'} // id='organization-profile' > - navigate('profile')}> - {/*// TODO*/} - - + {isAdmin ? navigate('profile')}>{profile} : profile} ); }; const OrganizationDangerSection = () => { - const { navigate } = useNavigate(); + const { organization, membership } = useCoreOrganization(); + const user = useCoreUser(); + + if (!organization || !membership) { + return null; + } + + const leave = () => { + return organization.removeMember(user.id); + }; return ( { variant='outline' colorScheme='danger' textVariant='buttonExtraSmallBold' + onClick={leave} + isDisabled={membership.role === 'admin'} > Leave organization - - Delete organization - ); diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/ProfileSettingsPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/ProfileSettingsPage.tsx index 8a88754c107..025a73a0852 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/ProfileSettingsPage.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/ProfileSettingsPage.tsx @@ -1,12 +1,14 @@ import React from 'react'; import { useWizard, Wizard } from '../../common'; +import { useCoreOrganization } from '../../contexts'; import { localizationKeys } from '../../customizables'; -import { AvatarUploader, Form, useCardState, withCardStateProvider } from '../../elements'; -import { useFormControl } from '../../utils'; +import { Form, useCardState, withCardStateProvider } from '../../elements'; +import { handleError, useFormControl } from '../../utils'; import { FormButtons } from '../UserProfile/FormButtons'; import { SuccessPage } from '../UserProfile/SuccessPage'; import { ContentPage } from './OrganizationContentPage'; +import { OrganizationProfileAvatarUploader } from './OrganizationProfileAvatarUploader'; export const ProfileSettingsPage = withCardStateProvider(() => { // const title = localizationKeys('userProfile.profilePage.title'); @@ -14,40 +16,36 @@ export const ProfileSettingsPage = withCardStateProvider(() => { const subtitle = 'Manage organization profile'; const card = useCardState(); const [avatarChanged, setAvatarChanged] = React.useState(false); + const { organization } = useCoreOrganization(); const wizard = useWizard({ onNextStep: () => card.setError(undefined) }); - const organizationName = useFormControl('name', 'placeholder' || '', { + const nameField = useFormControl('name', organization?.name || '', { type: 'text', - // label: localizationKeys('formFieldLabel__firstName'), - // placeholder: localizationKeys('formFieldInputPlaceholder__firstName'), - label: 'Organization name', - placeholder: '', + label: localizationKeys('formFieldLabel__organizationName'), + placeholder: localizationKeys('formFieldInputPlaceholder__organizationName'), }); - const dataChanged = 'placeholder' !== organizationName.value; + if (!organization) { + return null; + } + + const dataChanged = organization.name !== nameField.value; const canSubmit = dataChanged || avatarChanged; const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); - wizard.nextStep(); - - // return ( - // dataChanged - // ? user.update({ firstName: organizationName.value, lastName: lastNameField.value }) - // : Promise.resolve() - // ) - // .then(() => { - // wizard.nextStep(); - // }) - // .catch(err => { - // handleError(err, [organizationName, lastNameField], card.setError); - // }); + return (dataChanged ? organization.update({ name: nameField.value }) : Promise.resolve()) + .then(wizard.nextStep) + .catch(err => { + handleError(err, [nameField], card.setError); + }); }; const uploadAvatar = (file: File) => { - console.log(file); - return Promise.resolve(); + return organization.setLogo({ file }).then(() => { + setAvatarChanged(true); + }); }; return ( @@ -57,10 +55,13 @@ export const ProfileSettingsPage = withCardStateProvider(() => { headerSubtitle={subtitle} > - + @@ -69,7 +70,7 @@ export const ProfileSettingsPage = withCardStateProvider(() => { ); diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx b/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx index 317042f8467..c6f022ebd17 100644 --- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx @@ -1,47 +1,54 @@ import { OrganizationResource } from '@clerk/types'; import React from 'react'; -import { useCoreUser, useEnvironment } from '../../contexts'; -import { Flex, Flow, Link, localizationKeys, Text, useAppearance } from '../../customizables'; -import { Action, BaseCard, OrganizationPreview, PersonalWorkspacePreview, PoweredByClerkText } from '../../elements'; -import { RootBox } from '../../elements/RootBox'; -import { CogFilled, Plus } from '../../icons'; -import { animations, PropsOfComponent } from '../../styledSystem'; import { - OrganizationActions, - OrganizationPreviewButton, - PersonalWorkspacePreviewButton, -} from './OtherOrganizationActions'; + useCoreClerk, + useCoreOrganization, + useCoreOrganizationList, + useCoreUser, + useOrganizationSwitcherContext, +} from '../../contexts'; +import { localizationKeys } from '../../customizables'; +import { Action, OrganizationPreview, PersonalWorkspacePreview, PopoverCard, useCardState } from '../../elements'; +import { RootBox } from '../../elements/RootBox'; +import { CogFilled } from '../../icons'; +import { PropsOfComponent } from '../../styledSystem'; +import { OrganizationActionList } from './OtherOrganizationActions'; type OrganizationSwitcherPopoverProps = { isOpen: boolean; close: () => void } & PropsOfComponent< - typeof OrganizationSwitcherCard + typeof PopoverCard.Root >; export const OrganizationSwitcherPopover = React.forwardRef( (props, ref) => { const { isOpen, close, ...rest } = props; - const { authConfig } = useEnvironment(); - // const organization = useCoreOrganization(); + const card = useCardState(); + const { openOrganizationProfile } = useCoreClerk(); + const { organization: currentOrg } = useCoreOrganization(); + const { isLoaded, setActive } = useCoreOrganizationList(); + const { showPersonalAccount, afterOrganizationCreationUrl } = useOrganizationSwitcherContext(); + const user = useCoreUser(); - /* Mocks */ - const organization = { name: 'Test Org', logoUrl: user.profileImageUrl } as OrganizationResource; - const hidePersonal = false; - const otherOrganizations = [ - { name: 'Test Org2', logoUrl: user.profileImageUrl }, - { name: 'Test Org3', logoUrl: user.profileImageUrl }, - { name: 'Test Org4', logoUrl: user.profileImageUrl }, - ] as OrganizationResource[]; - const handleOrganizationClicked = (_organization: OrganizationResource) => () => { - close(); + if (!isLoaded) { + return null; + } + + const handleOrganizationClicked = (organization: OrganizationResource) => { + return card.runAsync(() => setActive({ organization })).then(close); }; + + const handlePersonalWorkspaceClicked = () => { + return card.runAsync(() => setActive({ organization: null })).then(close); + }; + const handleCreateOrganizationClicked = () => { + openOrganizationProfile({ new: true, afterOrganizationCreationUrl }); close(); }; + const handleManageOrganizationClicked = () => { - close(); - }; - const handlePersonalWorkspaceClicked = () => { + openOrganizationProfile(); close(); }; @@ -51,14 +58,6 @@ export const OrganizationSwitcherPopover = React.forwardRef ); - const createOrganizationButton = ( - - ); - - const organizationActions = authConfig.singleSessionMode ? null : otherOrganizations.length > 0 ? ( - <> - - {organization && !hidePersonal && ( - ({ - marginBottom: t.space.$4, - })} - onClick={handlePersonalWorkspaceClicked} - /> - )} - ({ - paddingLeft: t.space.$6, - marginBottom: t.space.$1, - textTransform: 'uppercase', - })} - localizationKey={localizationKeys('organizationSwitcher.title')} - /> - {otherOrganizations.map(organization => ( - - ))} - {createOrganizationButton} - - - ) : ( - {createOrganizationButton} - ); - return ( - - + -
- {organization ? ( + + {currentOrg ? ( <> ({ padding: `0 ${theme.space.$6}` })} /> {manageOrganizationButton} ) : ( - !hidePersonal && ( + showPersonalAccount && ( ({ padding: `0 ${theme.space.$6}`, marginBottom: theme.space.$6 })} /> ) )} - {organizationActions} -
-