Skip to content

Commit cf2bcee

Browse files
refactor(mosaic): move profile navigation state into controllers
1 parent e31fc63 commit cf2bcee

5 files changed

Lines changed: 110 additions & 60 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useState } from 'react';
2+
3+
import { organizationProfilePageIds } from './organization-profile.layout';
4+
import type { OrganizationProfilePageListOptions, OrganizationProfileViewProps } from './organization-profile.view';
5+
6+
type OrganizationProfileControllerOptions = Pick<OrganizationProfilePageListOptions, 'customPages' | 'pageOrder'> & {
7+
pages?: OrganizationProfilePageListOptions['pages'];
8+
defaultPage?: OrganizationProfileViewProps['activePage'];
9+
};
10+
11+
export function useOrganizationProfileController({
12+
pages,
13+
customPages,
14+
pageOrder,
15+
defaultPage,
16+
}: OrganizationProfileControllerOptions) {
17+
const [activePage, setActivePage] = useState<OrganizationProfileViewProps['activePage']>(
18+
defaultPage ?? pageOrder?.[0] ?? 'general',
19+
);
20+
21+
if (pages) {
22+
const pageIds = organizationProfilePageIds({ pages, customPages, pageOrder });
23+
const firstPage = pageIds[0];
24+
if (firstPage !== undefined && !pageIds.includes(activePage)) {
25+
setActivePage(firstPage);
26+
}
27+
}
28+
29+
return { activePage, onPageChange: setActivePage };
30+
}

‎packages/mosaic/src/features/organization-profile/organization-profile.layout.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
OrganizationProfilePageId,
1212
OrganizationProfilePages,
1313
} from './organization-profile.types';
14+
import type { OrganizationProfilePageListOptions } from './organization-profile.view';
1415

1516
export const ORGANIZATION_PROFILE_PAGE_IDS: readonly OrganizationProfilePageId[] = [
1617
'general',
@@ -56,3 +57,13 @@ export function resolveOrganizationProfilePages(
5657
];
5758
return applyOrder(order, entries, entry => entry.id);
5859
}
60+
61+
export function organizationProfilePageIds({
62+
pages,
63+
customPages,
64+
pageOrder,
65+
}: OrganizationProfilePageListOptions): string[] {
66+
return resolveOrganizationProfilePages(getAvailableOrganizationProfilePages(pages), customPages, pageOrder).map(
67+
entry => entry.id,
68+
);
69+
}

‎packages/mosaic/src/features/organization-profile/organization-profile.tsx‎

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
'use client';
22

33
import type { ReactNode } from 'react';
4-
import { useState } from 'react';
54

65
import type { APIKeysPageProps } from '../api-keys/api-keys-table';
7-
import { getAvailableOrganizationProfilePages, resolveOrganizationProfilePages } from './organization-profile.layout';
6+
import { useOrganizationProfileController } from './organization-profile.controller';
87
import { useOrganizationProfileModel } from './organization-profile.model';
98
import type { OrganizationProfileOptions } from './organization-profile.provider';
109
import { OrganizationProfileProvider } from './organization-profile.provider';
1110
import type { OrganizationProfilePageId, OrganizationProfilePages } from './organization-profile.types';
12-
import type { OrganizationProfilePageListOptions, OrganizationProfileViewProps } from './organization-profile.view';
11+
import type { OrganizationProfileViewProps } from './organization-profile.view';
1312
import { OrganizationProfileView } from './organization-profile.view';
1413
import { OrganizationProfileApiKeysPanel } from './organization-profile-api-keys-panel';
1514
import { OrganizationProfileGeneralPanel } from './organization-profile-general-panel';
@@ -88,30 +87,21 @@ export function OrganizationProfile({
8887
apiKeysProps,
8988
}: OrganizationProfileProps) {
9089
const model = useOrganizationProfileModel();
91-
const [activePage, setActivePage] = useState<OrganizationProfileViewProps['activePage']>(
92-
defaultPage ?? pageOrder?.[0] ?? 'general',
93-
);
90+
const pages = model.status === 'ready' ? organizationProfilePanels(model.pages, apiKeysProps) : undefined;
91+
const navigation = useOrganizationProfileController({ pages, customPages, pageOrder, defaultPage });
9492

9593
if (model.status === 'loading') {
9694
return fallback ?? null;
9795
}
98-
if (model.status === 'hidden') {
96+
if (!pages) {
9997
return null;
10098
}
10199

102-
const pages = organizationProfilePanels(model.pages, apiKeysProps);
103-
const pageIds = organizationProfilePageIds({ pages, customPages, pageOrder });
104-
const firstPage = pageIds[0];
105-
if (firstPage !== undefined && !pageIds.includes(activePage)) {
106-
setActivePage(firstPage);
107-
}
108-
109100
return (
110101
<OrganizationProfileProvider afterLeaveOrganizationUrl={afterLeaveOrganizationUrl}>
111102
<OrganizationProfileView
112103
elevation='flush'
113-
activePage={activePage}
114-
onPageChange={setActivePage}
104+
{...navigation}
115105
customPages={customPages}
116106
pageOrder={pageOrder}
117107
pages={pages}
@@ -135,13 +125,3 @@ export function organizationProfilePanels(
135125
available.includes('apiKeys') && !hideAPIKeys ? <OrganizationProfileApiKeysPanel {...apiKeysProps} /> : undefined,
136126
};
137127
}
138-
139-
export function organizationProfilePageIds({
140-
pages,
141-
customPages,
142-
pageOrder,
143-
}: OrganizationProfilePageListOptions): string[] {
144-
return resolveOrganizationProfilePages(getAvailableOrganizationProfilePages(pages), customPages, pageOrder).map(
145-
entry => entry.id,
146-
);
147-
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { useState } from 'react';
2+
3+
import { organizationProfilePageIds } from '../organization-profile/organization-profile.layout';
4+
import {
5+
getAvailableUserProfilePages,
6+
ORGANIZATION_PAGE_PREFIX,
7+
resolveUserProfilePages,
8+
withoutOrganizationPaths,
9+
} from './user-profile.layout';
10+
import type { UserProfileViewProps } from './user-profile.view';
11+
12+
type UserProfileControllerOptions = Pick<UserProfileViewProps, 'pages' | 'customPages' | 'pageOrder'> & {
13+
initialPage: UserProfileViewProps['activePage'];
14+
organization?:
15+
| { status: 'loading' | 'hidden' }
16+
| { status: 'ready'; pageList: NonNullable<UserProfileViewProps['organization']> };
17+
};
18+
19+
export function useUserProfileController({
20+
initialPage,
21+
pages,
22+
customPages,
23+
pageOrder,
24+
organization,
25+
}: UserProfileControllerOptions) {
26+
const [activePage, setActivePage] = useState(initialPage);
27+
28+
if (organization && organization.status !== 'loading' && activePage.startsWith(ORGANIZATION_PAGE_PREFIX)) {
29+
const organizationPageIds =
30+
organization.status === 'ready'
31+
? organizationProfilePageIds(organization.pageList).map(id => `${ORGANIZATION_PAGE_PREFIX}${id}`)
32+
: [];
33+
if (!organizationPageIds.includes(activePage)) {
34+
setActivePage(
35+
resolveUserProfilePages(
36+
getAvailableUserProfilePages(pages),
37+
withoutOrganizationPaths(customPages),
38+
pageOrder,
39+
)[0]?.id ?? 'account',
40+
);
41+
}
42+
}
43+
44+
return { activePage, onPageChange: setActivePage };
45+
}

‎packages/mosaic/src/features/user-profile/user-profile.tsx‎

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
11
'use client';
22

33
import type { ReactNode } from 'react';
4-
import { useState } from 'react';
54

65
import type { APIKeysPageProps } from '../api-keys/api-keys-table';
76
import type { OrganizationProfileProps } from '../organization-profile/organization-profile';
8-
import { organizationProfilePageIds, organizationProfilePanels } from '../organization-profile/organization-profile';
7+
import { organizationProfilePanels } from '../organization-profile/organization-profile';
98
import { useOrganizationProfileModel } from '../organization-profile/organization-profile.model';
109
import { OrganizationProfileProvider } from '../organization-profile/organization-profile.provider';
1110
import { useUserProfilePages } from '../switcher/switcher.pages';
1211
import type { SwitcherMode } from '../switcher/switcher.types';
13-
import {
14-
getAvailableUserProfilePages,
15-
ORGANIZATION_PAGE_PREFIX,
16-
resolveUserProfilePages,
17-
withoutOrganizationPaths,
18-
} from './user-profile.layout';
12+
import { useUserProfileController } from './user-profile.controller';
1913
import type { UserProfileOptions } from './user-profile.provider';
2014
import { UserProfileProvider } from './user-profile.provider';
2115
import type { UserProfileViewProps } from './user-profile.view';
@@ -175,18 +169,17 @@ export function UserProfile({
175169
);
176170
}
177171

178-
type AccountViewProps = Omit<UserProfileViewProps, 'activePage' | 'onPageChange'> & {
172+
type AccountViewProps = Omit<UserProfileViewProps, 'activePage' | 'onPageChange' | 'organization'> & {
179173
initialPage: UserProfileViewProps['activePage'];
180174
};
181175

182176
function AccountUserProfileView({ initialPage, ...viewProps }: AccountViewProps) {
183-
const [activePage, setActivePage] = useState(initialPage);
177+
const navigation = useUserProfileController({ initialPage, ...viewProps });
184178

185179
return (
186180
<UserProfileView
187181
{...viewProps}
188-
activePage={activePage}
189-
onPageChange={setActivePage}
182+
{...navigation}
190183
/>
191184
);
192185
}
@@ -197,34 +190,25 @@ function CombinedUserProfileView({
197190
...viewProps
198191
}: AccountViewProps & Pick<UserProfileProps, 'organizationProfileProps'>) {
199192
const model = useOrganizationProfileModel();
200-
const [activePage, setActivePage] = useState(initialPage);
201-
const organization =
193+
const organizationState =
202194
model.status === 'ready'
203-
? { pages: organizationProfilePanels(model.pages, apiKeysProps), customPages, pageOrder }
204-
: undefined;
205-
206-
if (model.status !== 'loading' && activePage.startsWith(ORGANIZATION_PAGE_PREFIX)) {
207-
const organizationPageIds = organization
208-
? organizationProfilePageIds(organization).map(id => `${ORGANIZATION_PAGE_PREFIX}${id}`)
209-
: [];
210-
if (!organizationPageIds.includes(activePage)) {
211-
setActivePage(
212-
resolveUserProfilePages(
213-
getAvailableUserProfilePages(viewProps.pages),
214-
withoutOrganizationPaths(viewProps.customPages),
215-
viewProps.pageOrder,
216-
)[0]?.id ?? 'account',
217-
);
218-
}
219-
}
195+
? {
196+
status: 'ready' as const,
197+
pageList: { pages: organizationProfilePanels(model.pages, apiKeysProps), customPages, pageOrder },
198+
}
199+
: { status: model.status };
200+
const navigation = useUserProfileController({
201+
initialPage,
202+
...viewProps,
203+
organization: organizationState,
204+
});
220205

221206
return (
222207
<OrganizationProfileProvider afterLeaveOrganizationUrl={afterLeaveOrganizationUrl}>
223208
<UserProfileView
224209
{...viewProps}
225-
activePage={activePage}
226-
onPageChange={setActivePage}
227-
organization={organization}
210+
{...navigation}
211+
organization={organizationState.status === 'ready' ? organizationState.pageList : undefined}
228212
/>
229213
</OrganizationProfileProvider>
230214
);

0 commit comments

Comments
 (0)