Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
753 changes: 753 additions & 0 deletions packages/ui/COMPOSED_API_PLAN.md

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@
"import": "./dist/themes/experimental.js",
"default": "./dist/themes/experimental.js"
},
"./experimental": {
"types": "./dist/experimental/index.d.ts",
"import": "./dist/experimental/index.js",
"default": "./dist/experimental/index.js"
},
"./themes/shadcn.css": "./dist/themes/shadcn.css",
"./register": {
"import": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const OrganizationBillingPageInternal = withCardStateProvider(() => {
<ProfileCard.Page>
<Col
elementDescriptor={descriptors.page}
sx={t => ({ gap: t.space.$8, color: t.colors.$colorForeground })}
sx={t => ({ gap: t.space.$8, color: t.colors.$colorForeground, isolation: 'isolate' })}
>
<Col
elementDescriptor={descriptors.profilePage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const OrganizationGeneralPage = () => {
<ProfileCard.Page>
<Col
elementDescriptor={descriptors.page}
sx={t => ({ gap: t.space.$8 })}
sx={t => ({ gap: t.space.$8, isolation: 'isolate' })}
>
<Col
elementDescriptor={descriptors.profilePage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const OrganizationMembers = withCardStateProvider(() => {
<Col
elementDescriptor={descriptors.page}
gap={2}
sx={{ isolation: 'isolate' }}
>
<Col
elementDescriptor={descriptors.profilePage}
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/components/UserProfile/APIKeysPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const APIKeysPage = () => {
<Col
gap={4}
elementDescriptor={descriptors.page}
sx={{ isolation: 'isolate' }}
>
<Header.Root>
<Header.Title
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/UserProfile/AccountPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const AccountPage = withCardStateProvider(() => {
<ProfileCard.Page>
<Col
elementDescriptor={descriptors.page}
sx={t => ({ gap: t.space.$8, color: t.colors.$colorForeground })}
sx={t => ({ gap: t.space.$8, color: t.colors.$colorForeground, isolation: 'isolate' })}
>
<Col
elementDescriptor={descriptors.profilePage}
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/UserProfile/BillingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const BillingPageInternal = withCardStateProvider(() => {
<ProfileCard.Page>
<Col
elementDescriptor={descriptors.page}
sx={t => ({ gap: t.space.$8, color: t.colors.$colorForeground })}
sx={t => ({ gap: t.space.$8, color: t.colors.$colorForeground, isolation: 'isolate' })}
>
<Col
elementDescriptor={descriptors.profilePage}
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/UserProfile/SecurityPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const SecurityPage = withCardStateProvider(() => {
<ProfileCard.Page>
<Col
elementDescriptor={descriptors.page}
sx={t => ({ gap: t.space.$8 })}
sx={t => ({ gap: t.space.$8, isolation: 'isolate' })}
>
<Col
elementDescriptor={descriptors.profilePage}
Expand Down
17 changes: 17 additions & 0 deletions packages/ui/src/composed/OrganizationProfile/APIKeys.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { lazy, Suspense } from 'react';

import { CardStateProvider } from '../../elements/contexts';

const OrganizationAPIKeysPage = lazy(() =>
import('../../components/OrganizationProfile/OrganizationAPIKeysPage').then(m => ({
default: m.OrganizationAPIKeysPage,
})),
);

export const APIKeys = () => (
<CardStateProvider>
<Suspense fallback={''}>
<OrganizationAPIKeysPage />
</Suspense>
</CardStateProvider>
);
53 changes: 53 additions & 0 deletions packages/ui/src/composed/OrganizationProfile/Billing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { lazy, Suspense } from 'react';

import { RouteContext } from '../../router/RouteContext';
import { useBillingRouter } from '../useBillingRouter';

const OrganizationBillingPage = lazy(() =>
import('../../components/OrganizationProfile/OrganizationBillingPage').then(m => ({
default: m.OrganizationBillingPage,
})),
);

const OrganizationPlansPage = lazy(() =>
import('../../components/OrganizationProfile/OrganizationPlansPage').then(m => ({
default: m.OrganizationPlansPage,
})),
);

const OrganizationStatementPage = lazy(() =>
import('../../components/OrganizationProfile/OrganizationStatementPage').then(m => ({
default: m.OrganizationStatementPage,
})),
);

const OrganizationPaymentAttemptPage = lazy(() =>
import('../../components/OrganizationProfile/OrganizationPaymentAttemptPage').then(m => ({
default: m.OrganizationPaymentAttemptPage,
})),
);

export const Billing = () => {
const { router, route } = useBillingRouter();

let content: React.ReactNode;
switch (route.page) {
case 'plans':
content = <OrganizationPlansPage />;
break;
case 'statement':
content = <OrganizationStatementPage />;
break;
case 'payment-attempt':
content = <OrganizationPaymentAttemptPage />;
break;
default:
content = <OrganizationBillingPage />;
}

return (
<RouteContext.Provider value={router}>
<Suspense fallback={''}>{content}</Suspense>
</RouteContext.Provider>
);
};
3 changes: 3 additions & 0 deletions packages/ui/src/composed/OrganizationProfile/General.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { OrganizationGeneralPage } from '../../components/OrganizationProfile/OrganizationGeneralPage';

export const General = () => <OrganizationGeneralPage />;
3 changes: 3 additions & 0 deletions packages/ui/src/composed/OrganizationProfile/Members.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { OrganizationMembers } from '../../components/OrganizationProfile/OrganizationMembers';

export const Members = () => <OrganizationMembers />;
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { ModuleManager } from '@clerk/shared/moduleManager';
import { useClerk, useOrganization, useUser } from '@clerk/shared/react';
import type { EnvironmentResource, OAuthProvider, OAuthScope } from '@clerk/shared/types';
import React from 'react';

import { AppearanceProvider } from '@/ui/customizables/AppearanceContext';
import { FlowMetadataProvider } from '@/ui/elements/contexts';
import type { Appearance } from '@/ui/internal/appearance';
import { RouteContext } from '@/ui/router/RouteContext';
import { InternalThemeProvider } from '@/ui/styledSystem';
import { StyleCacheProvider } from '@/ui/styledSystem/StyleCacheProvider';

import { EnvironmentProvider } from '../../contexts/EnvironmentContext';
import { ModuleManagerProvider } from '../../contexts/ModuleManagerContext';
import { OptionsProvider } from '../../contexts/OptionsContext';
import { SubscriberTypeContext } from '../../contexts/components/SubscriberType';
import { OrganizationProfileContext } from '../../contexts/components/OrganizationProfile';
import { ProfileCardPagePaddingProvider } from '../../elements/ProfileCard';
import { stubRouter } from '../stubRouter';

const stubModuleManager: ModuleManager = {
import: () => Promise.resolve(undefined),
};

type OrganizationProfileProviderProps = React.PropsWithChildren<{
appearance?: Appearance;
additionalOAuthScopes?: Partial<Record<OAuthProvider, OAuthScope[]>>;
}>;

export const OrganizationProfileProvider = (props: OrganizationProfileProviderProps) => {
const { children, appearance } = props;
const clerk = useClerk();
const { isLoaded, user } = useUser();
const { organization } = useOrganization();

const environment = (clerk as any).__internal_environment as EnvironmentResource | null | undefined;

if (!isLoaded || !user || !organization || !environment) {
return null;
}

const orgProfileCtxValue = {
componentName: 'OrganizationProfile' as const,
mode: 'mounted' as const,
routing: 'hash' as const,
path: undefined,
customPages: [],
};

return (
<StyleCacheProvider>
<AppearanceProvider
appearanceKey='organizationProfile'
appearance={appearance}
>
<FlowMetadataProvider flow='organizationProfile'>
<InternalThemeProvider>
<ModuleManagerProvider moduleManager={stubModuleManager}>
<OptionsProvider value={{}}>
<EnvironmentProvider value={environment}>
<RouteContext.Provider value={stubRouter}>
<SubscriberTypeContext.Provider value='organization'>
<OrganizationProfileContext.Provider value={orgProfileCtxValue}>
<ProfileCardPagePaddingProvider value={false}>{children}</ProfileCardPagePaddingProvider>
</OrganizationProfileContext.Provider>
</SubscriberTypeContext.Provider>
</RouteContext.Provider>
</EnvironmentProvider>
</OptionsProvider>
</ModuleManagerProvider>
</InternalThemeProvider>
</FlowMetadataProvider>
</AppearanceProvider>
</StyleCacheProvider>
);
};
13 changes: 13 additions & 0 deletions packages/ui/src/composed/OrganizationProfile/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { APIKeys } from './APIKeys';
import { Billing } from './Billing';
import { General } from './General';
import { Members } from './Members';
import { OrganizationProfileProvider } from './OrganizationProfileProvider';

export const OrganizationProfile = {
Provider: OrganizationProfileProvider,
General,
Members,
Billing,
APIKeys,
};
17 changes: 17 additions & 0 deletions packages/ui/src/composed/UserProfile/APIKeys.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { lazy, Suspense } from 'react';

import { CardStateProvider } from '../../elements/contexts';

const APIKeysPage = lazy(() =>
import('../../components/UserProfile/APIKeysPage').then(m => ({
default: m.APIKeysPage,
})),
);

export const APIKeys = () => (
<CardStateProvider>
<Suspense fallback={''}>
<APIKeysPage />
</Suspense>
</CardStateProvider>
);
3 changes: 3 additions & 0 deletions packages/ui/src/composed/UserProfile/Account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { AccountPage } from '../../components/UserProfile/AccountPage';

export const Account = () => <AccountPage />;
53 changes: 53 additions & 0 deletions packages/ui/src/composed/UserProfile/Billing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { lazy, Suspense } from 'react';

import { RouteContext } from '../../router/RouteContext';
import { useBillingRouter } from '../useBillingRouter';

const BillingPage = lazy(() =>
import('../../components/UserProfile/BillingPage').then(m => ({
default: m.BillingPage,
})),
);

const PlansPage = lazy(() =>
import('../../components/UserProfile/PlansPage').then(m => ({
default: m.PlansPage,
})),
);

const StatementPage = lazy(() =>
import('../../components/Statements').then(m => ({
default: m.StatementPage,
})),
);

const PaymentAttemptPage = lazy(() =>
import('../../components/PaymentAttempts').then(m => ({
default: m.PaymentAttemptPage,
})),
);

export const Billing = () => {
const { router, route } = useBillingRouter();

let content: React.ReactNode;
switch (route.page) {
case 'plans':
content = <PlansPage />;
break;
case 'statement':
content = <StatementPage />;
break;
case 'payment-attempt':
content = <PaymentAttemptPage />;
break;
default:
content = <BillingPage />;
}

return (
<RouteContext.Provider value={router}>
<Suspense fallback={''}>{content}</Suspense>
</RouteContext.Provider>
);
};
3 changes: 3 additions & 0 deletions packages/ui/src/composed/UserProfile/Security.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { SecurityPage } from '../../components/UserProfile/SecurityPage';

export const Security = () => <SecurityPage />;
73 changes: 73 additions & 0 deletions packages/ui/src/composed/UserProfile/UserProfileProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { ModuleManager } from '@clerk/shared/moduleManager';
import { useClerk, useUser } from '@clerk/shared/react';
import type { EnvironmentResource, OAuthProvider, OAuthScope } from '@clerk/shared/types';
import React from 'react';

import { AppearanceProvider } from '@/ui/customizables/AppearanceContext';
import { FlowMetadataProvider } from '@/ui/elements/contexts';
import type { Appearance } from '@/ui/internal/appearance';
import { RouteContext } from '@/ui/router/RouteContext';
import { InternalThemeProvider } from '@/ui/styledSystem';
import { StyleCacheProvider } from '@/ui/styledSystem/StyleCacheProvider';

import { EnvironmentProvider } from '../../contexts/EnvironmentContext';
import { ModuleManagerProvider } from '../../contexts/ModuleManagerContext';
import { OptionsProvider } from '../../contexts/OptionsContext';
import { UserProfileContext } from '../../contexts/components/UserProfile';
import { ProfileCardPagePaddingProvider } from '../../elements/ProfileCard';
import { stubRouter } from '../stubRouter';

const stubModuleManager: ModuleManager = {
import: () => Promise.resolve(undefined),
};

type UserProfileProviderProps = React.PropsWithChildren<{
appearance?: Appearance;
additionalOAuthScopes?: Partial<Record<OAuthProvider, OAuthScope[]>>;
}>;

export const UserProfileProvider = (props: UserProfileProviderProps) => {
const { children, appearance, additionalOAuthScopes } = props;
const clerk = useClerk();
const { isLoaded, user } = useUser();

const environment = (clerk as any).__internal_environment as EnvironmentResource | null | undefined;

if (!isLoaded || !user || !environment) {
return null;
}

const userProfileCtxValue = {
componentName: 'UserProfile' as const,
mode: 'mounted' as const,
routing: 'hash' as const,
path: undefined,
additionalOAuthScopes,
customPages: [],
};

return (
<StyleCacheProvider>
<AppearanceProvider
appearanceKey='userProfile'
appearance={appearance}
>
<FlowMetadataProvider flow='userProfile'>
<InternalThemeProvider>
<ModuleManagerProvider moduleManager={stubModuleManager}>
<OptionsProvider value={{}}>
<EnvironmentProvider value={environment}>
<RouteContext.Provider value={stubRouter}>
<UserProfileContext.Provider value={userProfileCtxValue}>
<ProfileCardPagePaddingProvider value={false}>{children}</ProfileCardPagePaddingProvider>
</UserProfileContext.Provider>
</RouteContext.Provider>
</EnvironmentProvider>
</OptionsProvider>
</ModuleManagerProvider>
</InternalThemeProvider>
</FlowMetadataProvider>
</AppearanceProvider>
</StyleCacheProvider>
);
};
Loading
Loading