Skip to content

Commit 21f7045

Browse files
committed
Prefill with org defaults
1 parent 12005fb commit 21f7045

File tree

6 files changed

+44
-27
lines changed

6 files changed

+44
-27
lines changed

packages/clerk-js/src/core/resources/Environment.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { APIKeySettings } from './APIKeySettings';
1616
import { AuthConfig, BaseResource, CommerceSettings, DisplayConfig, ProtectConfig, UserSettings } from './internal';
1717
import { OrganizationSettings } from './OrganizationSettings';
1818

19+
// TODO -> Update with new flag for default orgs
20+
// Use it to conditionally trigger query
1921
export class Environment extends BaseResource implements EnvironmentResource {
2022
private static instance: Environment;
2123

packages/clerk-js/src/core/resources/User.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
UserOrganizationInvitation,
5454
Web3Wallet,
5555
} from './internal';
56+
import { OrganizationCreationDefaults } from './OrganizationCreationDefaults';
5657

5758
export class User extends BaseResource implements UserResource {
5859
pathRoot = '/me';
@@ -275,6 +276,8 @@ export class User extends BaseResource implements UserResource {
275276
getOrganizationMemberships: GetOrganizationMemberships = retrieveMembership =>
276277
OrganizationMembership.retrieve(retrieveMembership);
277278

279+
getOrganizationCreationDefaults = () => OrganizationCreationDefaults.retrieve();
280+
278281
leaveOrganization = async (organizationId: string): Promise<DeletedObjectResource> => {
279282
const json = (
280283
await BaseResource._fetch<DeletedObjectJSON>({

packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useOrganizationList } from '@clerk/shared/react';
2-
import type { CreateOrganizationParams } from '@clerk/shared/types';
2+
import type { CreateOrganizationParams, OrganizationCreationDefaultsResource } from '@clerk/shared/types';
33
import { useState } from 'react';
44

55
import { useEnvironment } from '@/ui/contexts';
@@ -20,24 +20,9 @@ import { OrganizationProfileAvatarUploader } from '../../../OrganizationProfile/
2020
import { organizationListParams } from '../../../OrganizationSwitcher/utils';
2121
import { OrganizationCreationDefaultsAlert } from './OrganizationCreationDefaultsAlert';
2222

23-
// TODO: Replace with actual API call to OrganizationCreationDefaults.retrieve()
24-
// TODO - Only replace if .organization_settings.organization_creation_defaults.enabled
25-
const organizationCreationDefaults = {
26-
advisory: {
27-
type: 'existing_org_with_domain' as const,
28-
severity: 'warning' as const,
29-
},
30-
form: {
31-
name: '',
32-
slug: '',
33-
logo: null,
34-
},
35-
pathRoot: '',
36-
reload: () => Promise.resolve({} as any),
37-
};
38-
3923
type CreateOrganizationScreenProps = {
4024
onCancel?: () => void;
25+
organizationCreationDefaults?: OrganizationCreationDefaultsResource;
4126
};
4227

4328
export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) => {
@@ -50,12 +35,15 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) =
5035
const { organizationSettings } = useEnvironment();
5136
const [file, setFile] = useState<File | null>();
5237

53-
const nameField = useFormControl('name', '', {
38+
// Show default logo only when no file action has been taken (file is undefined)
39+
const defaultLogoUrl = file === undefined ? props.organizationCreationDefaults?.form.logo : undefined;
40+
41+
const nameField = useFormControl('name', props.organizationCreationDefaults?.form.name ?? '', {
5442
type: 'text',
5543
label: localizationKeys('taskChooseOrganization.createOrganization.formFieldLabel__name'),
5644
placeholder: localizationKeys('taskChooseOrganization.createOrganization.formFieldInputPlaceholder__name'),
5745
});
58-
const slugField = useFormControl('slug', '', {
46+
const slugField = useFormControl('slug', props.organizationCreationDefaults?.form.slug ?? '', {
5947
type: 'text',
6048
label: localizationKeys('taskChooseOrganization.createOrganization.formFieldLabel__slug'),
6149
placeholder: localizationKeys('taskChooseOrganization.createOrganization.formFieldInputPlaceholder__slug'),
@@ -81,6 +69,11 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) =
8169

8270
if (file) {
8371
await organization.setLogo({ file });
72+
} else if (defaultLogoUrl) {
73+
const response = await fetch(defaultLogoUrl);
74+
const blob = await response.blob();
75+
const logoFile = new File([blob], 'logo', { type: blob.type });
76+
await organization.setLogo({ file: logoFile });
8477
}
8578

8679
await setActive({
@@ -122,11 +115,11 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) =
122115

123116
<FormContainer sx={t => ({ padding: `${t.space.$none} ${t.space.$10} ${t.space.$8}` })}>
124117
<Form.Root onSubmit={onSubmit}>
125-
<OrganizationCreationDefaultsAlert organizationCreationDefaults={organizationCreationDefaults} />
118+
<OrganizationCreationDefaultsAlert organizationCreationDefaults={props.organizationCreationDefaults} />
126119
<OrganizationProfileAvatarUploader
127-
organization={{ name: nameField.value }}
120+
organization={{ name: nameField.value, imageUrl: defaultLogoUrl ?? undefined }}
128121
onAvatarChange={async file => await setFile(file)}
129-
onAvatarRemove={file ? onAvatarRemove : null}
122+
onAvatarRemove={file || defaultLogoUrl ? onAvatarRemove : null}
130123
avatarPreviewPlaceholder={
131124
<IconButton
132125
variant='ghost'

packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/OrganizationCreationDefaultsAlert.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { Alert } from '@/ui/elements/Alert';
66
export function OrganizationCreationDefaultsAlert({
77
organizationCreationDefaults,
88
}: {
9-
organizationCreationDefaults: OrganizationCreationDefaultsResource;
9+
organizationCreationDefaults?: OrganizationCreationDefaultsResource;
1010
}) {
11-
if (!organizationCreationDefaults.advisory) {
11+
if (!organizationCreationDefaults?.advisory) {
1212
return null;
1313
}
1414

@@ -20,6 +20,8 @@ export function OrganizationCreationDefaultsAlert({
2020
);
2121
}
2222

23+
// TODO -> Update with latest advisory where meta is returned
24+
// TODO -> Include email domain in message
2325
const advisoryTypeToLocalizationKey: Record<OrganizationCreationAdvisoryType, LocalizationKey> = {
2426
existing_org_with_domain: localizationKeys('taskChooseOrganization.alerts.existingOrgWithDomain'),
2527
};

packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/index.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useClerk, useSession, useUser } from '@clerk/shared/react';
2+
import type { OrganizationCreationDefaultsResource } from '@clerk/shared/types';
23
import { useState } from 'react';
34

5+
import { useFetch } from '@/hooks';
46
import { useSignOutContext, withCoreSessionSwitchGuard } from '@/ui/contexts';
57
import { descriptors, Flex, Flow, localizationKeys, Spinner } from '@/ui/customizables';
68
import { Card } from '@/ui/elements/Card';
@@ -19,6 +21,11 @@ const TaskChooseOrganizationInternal = () => {
1921
const { userMemberships, userSuggestions, userInvitations } = useOrganizationListInView();
2022
const { otherSessions } = useMultipleSessions({ user });
2123
const { navigateAfterSignOut, navigateAfterMultiSessionSingleSignOutUrl } = useSignOutContext();
24+
const organizationCreationDefaults = useFetch(
25+
user?.getOrganizationCreationDefaults,
26+
'organization-creation-defaults',
27+
{ staleTime: Infinity },
28+
);
2229

2330
const handleSignOut = () => {
2431
if (otherSessions.length === 0) {
@@ -28,7 +35,11 @@ const TaskChooseOrganizationInternal = () => {
2835
return signOut(navigateAfterMultiSessionSingleSignOutUrl, { sessionId: session?.id });
2936
};
3037

31-
const isLoading = userMemberships?.isLoading || userInvitations?.isLoading || userSuggestions?.isLoading;
38+
const isLoading =
39+
userMemberships?.isLoading ||
40+
userInvitations?.isLoading ||
41+
userSuggestions?.isLoading ||
42+
organizationCreationDefaults.isLoading;
3243
const hasExistingResources = !!(userMemberships?.count || userInvitations?.count || userSuggestions?.count);
3344
const identifier = user?.primaryEmailAddress?.emailAddress ?? user?.username;
3445

@@ -54,7 +65,10 @@ const TaskChooseOrganizationInternal = () => {
5465
/>
5566
</Flex>
5667
) : (
57-
<TaskChooseOrganizationFlows initialFlow={hasExistingResources ? 'choose' : 'create'} />
68+
<TaskChooseOrganizationFlows
69+
initialFlow={hasExistingResources ? 'choose' : 'create'}
70+
organizationCreationDefaults={organizationCreationDefaults.data}
71+
/>
5872
)}
5973
</Card.Content>
6074

@@ -88,6 +102,7 @@ const TaskChooseOrganizationInternal = () => {
88102

89103
type TaskChooseOrganizationFlowsProps = {
90104
initialFlow: 'create' | 'choose';
105+
organizationCreationDefaults?: OrganizationCreationDefaultsResource;
91106
};
92107

93108
const TaskChooseOrganizationFlows = withCardStateProvider((props: TaskChooseOrganizationFlowsProps) => {
@@ -97,6 +112,7 @@ const TaskChooseOrganizationFlows = withCardStateProvider((props: TaskChooseOrga
97112
return (
98113
<CreateOrganizationScreen
99114
onCancel={props.initialFlow === 'choose' ? () => setCurrentFlow('choose') : undefined}
115+
organizationCreationDefaults={props.organizationCreationDefaults}
100116
/>
101117
);
102118
}

packages/ui/src/elements/AvatarUploader.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ export const AvatarUploader = (props: AvatarUploaderProps) => {
9090
await handleFileDrop(f);
9191
};
9292

93+
const hasExistingImage = !!(avatarPreview.props as { imageUrl?: string })?.imageUrl;
9394
const previewElement = objectUrl
9495
? React.cloneElement(avatarPreview, { imageUrl: objectUrl })
95-
: avatarPreviewPlaceholder
96+
: avatarPreviewPlaceholder && !hasExistingImage
9697
? React.cloneElement(avatarPreviewPlaceholder, { onClick: openDialog })
9798
: avatarPreview;
9899

0 commit comments

Comments
 (0)